diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a3d239c8420..72b28f9aca9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Fixed a double free when setting a property on a JavaScript object fails, which application script could trigger while the request object was being built. Such failures are now reported as a failed request (#8356). - Historical states retrieved by JavaScript endpoints, through `ccf.historicalState` or `ccf.historical.getStateRange`, remain available through response conversion and are released when the request completes, rather than being retained for the lifetime of the node (#8355). +- JavaScript `verifySnpAttestation()` and the deprecated C++ `ccf::pal::snp::Attestation` returned swapped `current_minor` and `current_build` values. Both now match the AMD SEV-SNP report layout, with `current_build` at offset `0x1E8` and `current_minor` at `0x1E9` (#8083). + +### Changed + +- SNP attestation reports are now parsed and verified through TAV. Decode a report with `ccf::pal::snp::parse_attestation_report_unverified()`, which returns `ccf::pal::snp::AttestationReport`, an owning smart pointer, and verify it against TAV and CCF's policy with `ccf::pal::verify_snp_attestation_report_and_get()`. Field accessors borrow the report's storage, so destroying or replacing the owner invalidates them. The packed `ccf::pal::snp::Attestation` wire-layout type and its accessors still work, but are deprecated (#8083). +- `ccf::pal::snp::get_attestation()` in `ccf/pal/snp_ioctl.h` is unchanged, but its `get()` accessor is deprecated. Call `get_raw()` instead for the unverified report bytes, then decode them with `parse_attestation_report_unverified()` (#8083). ## [7.0.15] diff --git a/CMakeLists.txt b/CMakeLists.txt index 5c542bc5dc75..9ada2f7d7a07 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -263,6 +263,10 @@ add_ccf_static_library( SRCS ${CCF_DIR}/src/pal/attestation.cpp LINK_LIBS ccfcrypto ) +target_include_directories( + ccf_pal + PRIVATE ${CCF_DIR}/3rdparty/internal/tee-attestation-verification/ffi/include +) # CCF js lib add_ccf_static_library( @@ -510,6 +514,11 @@ install( PATTERN "*.inc" ) +install( + FILES ${TAV_INCLUDE_DIR}/tav/snp.h ${TAV_INCLUDE_DIR}/tav/utils.h + DESTINATION include/3rdparty/tav +) + # Install all private CCF headers, which may still be needed install( DIRECTORY src/ @@ -598,6 +607,7 @@ if(BUILD_TESTS) snp_ioctl_test ${CMAKE_CURRENT_SOURCE_DIR}/src/pal/test/snp_ioctl_test.cpp ) + target_link_libraries(snp_ioctl_test PRIVATE ccf_pal) set_property(TEST snp_ioctl_test APPEND PROPERTY LABELS snp) set_property(TEST snp_ioctl_test APPEND PROPERTY CONFIGURATIONS snp) diff --git a/cmake/ccf_rs.cmake b/cmake/ccf_rs.cmake index d24e557d8c0e..eecf172092c8 100644 --- a/cmake/ccf_rs.cmake +++ b/cmake/ccf_rs.cmake @@ -75,10 +75,29 @@ add_custom_target( "${CCF_RS_DIR}/rust-toolchain.toml" "${CCF_DIR}/src/cose/cose_rs/Cargo.toml" "${CCF_DIR}/3rdparty/internal/cose-openssl/Cargo.toml" + "${CCF_DIR}/3rdparty/internal/tee-attestation-verification/ffi/Cargo.toml" COMMENT "Building ${CCF_RS_PACKAGE} Rust static library (Cargo profile: ${CCF_RS_CARGO_PROFILE_NAME})" USES_TERMINAL VERBATIM ) +add_library(ccf_rs INTERFACE) +target_link_libraries( + ccf_rs + INTERFACE + $ + $ + ssl + crypto +) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + target_link_libraries( + ccf_rs + INTERFACE ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS} m + ) +endif() +add_dependencies(ccf_rs cargo-build_ccf_rs) + install(FILES "${CCF_RS_LIB_BUILD_PATH}" DESTINATION lib) +install(TARGETS ccf_rs EXPORT ccf) diff --git a/cmake/crypto.cmake b/cmake/crypto.cmake index da483a15ee08..b18a33f19205 100644 --- a/cmake/crypto.cmake +++ b/cmake/crypto.cmake @@ -43,13 +43,7 @@ add_hardening(ccfcrypto) add_tidy(ccfcrypto) target_link_libraries(ccfcrypto PUBLIC crypto ssl ccf_threading) -target_link_libraries( - ccfcrypto - PUBLIC - $ - $ -) -add_dependencies(ccfcrypto cargo-build_ccf_rs) +target_link_libraries(ccfcrypto PUBLIC ccf_rs) set_property(TARGET ccfcrypto PROPERTY POSITION_INDEPENDENT_CODE ON) install(TARGETS ccfcrypto EXPORT ccf DESTINATION lib) diff --git a/include/ccf/node/quote.h b/include/ccf/node/quote.h index ac9e983ab0ce..b28442e045ec 100644 --- a/include/ccf/node/quote.h +++ b/include/ccf/node/quote.h @@ -39,8 +39,15 @@ namespace ccf static std::optional get_host_data(const QuoteInfo& quote_info); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + [[deprecated("Use get_snp_attestation_report")]] static std::optional get_snp_attestation( const QuoteInfo& quote_info); +#pragma GCC diagnostic pop + + static std::optional + get_snp_attestation_report(const QuoteInfo& quote_info); static QuoteVerificationResult verify_quote_against_store( ccf::kv::ReadOnlyTx& tx, diff --git a/include/ccf/pal/attestation.h b/include/ccf/pal/attestation.h index 19fc6ed55ab1..d3f1fbb7e4ee 100644 --- a/include/ccf/pal/attestation.h +++ b/include/ccf/pal/attestation.h @@ -3,6 +3,7 @@ #pragma once #include "ccf/ds/quote_info.h" +#include "ccf/pal/attestation_sev_snp.h" #include "ccf/pal/attestation_sev_snp_endorsements.h" #include "ccf/pal/measurement.h" #include "ccf/pal/report_data.h" @@ -28,6 +29,12 @@ namespace ccf::pal PlatformAttestationMeasurement& measurement, PlatformAttestationReportData& report_data); + /// Verify with TAV, then enforce CCF's SNP attestation policy. + snp::AttestationReport verify_snp_attestation_report_and_get( + const QuoteInfo& quote_info, + PlatformAttestationMeasurement& measurement, + PlatformAttestationReportData& report_data); + void verify_quote( const QuoteInfo& quote_info, PlatformAttestationMeasurement& measurement, diff --git a/include/ccf/pal/attestation_sev_snp.h b/include/ccf/pal/attestation_sev_snp.h index 600ed237a403..fafae2660b17 100644 --- a/include/ccf/pal/attestation_sev_snp.h +++ b/include/ccf/pal/attestation_sev_snp.h @@ -15,9 +15,13 @@ #include #include #include +#include #include +#include #include #include +#include +#include #include namespace ccf::pal::snp @@ -28,6 +32,7 @@ namespace ccf::pal::snp static constexpr auto NO_SECURITY_POLICY = ""; // From https://developer.amd.com/sev/ + [[deprecated("TAV verifies AMD root signing keys internally")]] constexpr auto amd_milan_root_signing_public_key = R"(-----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0Ld52RJOdeiJlqK2JdsV @@ -44,6 +49,7 @@ pCCoMNit2uLo9M18fHz10lOMT8nWAUvRZFzteXCm+7PHdYPlmQwUw3LvenJ/ILXo QPHfbkH0CyPfhl1jWhJFZasCAwEAAQ== -----END PUBLIC KEY----- )"; + [[deprecated("TAV verifies AMD root signing keys internally")]] constexpr auto amd_genoa_root_signing_public_key = R"(-----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA3Cd95S/uFOuRIskW9vz9 @@ -60,6 +66,7 @@ HP1qYrnvhzaG1S70vw6OkbaaC9EjiH/uHgAJQGxon7u0Q7xgoREWA/e7JcBQwLg8 0Hq/sbRuqesxz7wBWSY254cCAwEAAQ== -----END PUBLIC KEY----- )"; + [[deprecated("TAV verifies AMD root signing keys internally")]] constexpr auto amd_turin_root_signing_public_key = R"(-----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwaAriB7EIuVc4ZB1wD3Y @@ -77,12 +84,14 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== -----END PUBLIC KEY----- )"; - struct AmdRootSigningKey + struct [[deprecated( + "TAV verifies AMD root signing keys internally")]] AmdRootSigningKey { const char* public_key; const char* issuer; }; + [[deprecated("TAV verifies AMD root signing keys internally")]] inline const std::map amd_root_signing_keys{ {ProductName::Milan, {amd_milan_root_signing_public_key, @@ -222,15 +231,14 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== TcbVersionRaw() = default; - TcbVersionRaw(const std::vector& data) + TcbVersionRaw(std::span data) { if (data.size() != snp_tcb_version_size) { throw std::logic_error( fmt::format("Invalid TCB version raw data size: {}", data.size())); } - std::memcpy( - static_cast(underlying_data), data.data(), snp_tcb_version_size); + std::memcpy(underlying_data, data.data(), snp_tcb_version_size); } [[nodiscard]] std::vector data() const @@ -392,6 +400,10 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== sizeof(PlatformInfo) == sizeof(uint64_t), "Cannot cast PlatformInfo to uint64_t"); + static constexpr size_t attestation_report_size = 1184; + + struct [[deprecated("Use ccf::pal::snp::AttestationReport")]] Attestation; + #pragma pack(push, 1) // Table 21 @@ -425,8 +437,8 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== uint8_t reserved1[21] = {0}; /* 0x18B */ uint8_t chip_id[64] = {0}; /* 0x1A0 */ TcbVersionRaw committed_tcb; /* 0x1E0 */ - uint8_t current_minor = 0; /* 0x1E8 */ - uint8_t current_build = 0; /* 0x1E9 */ + uint8_t current_build = 0; /* 0x1E8 */ + uint8_t current_minor = 0; /* 0x1E9 */ uint8_t current_major = 0; /* 0x1EA */ uint8_t reserved2 = 0; /* 0x1EB */ uint8_t committed_build = 0; /* 0x1EC */ @@ -456,6 +468,49 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== }; #pragma pack(pop) + // Reports are allocated in Rust and must be freed through TAV, not C++ + // delete. A stateless deleter keeps the smart pointer default-constructible + // without storing a cleanup function pointer. + struct AttestationReportDeleter + { + void operator()(TavSnpAttestationReport* report) const noexcept + { + tav_snp_attestation_report_free(report); + } + }; + + using AttestationReport = + std::unique_ptr; + + inline std::span get_chip_id_for_vcek( + const AttestationReport& report) + { + if (report == nullptr) + { + throw std::logic_error("Cannot access an empty SNP attestation report"); + } + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_chip_id(report.get(), &data, &size); + const auto chip_id = std::span{data, size}; + const auto product = get_sev_snp_product( + tav_snp_attestation_report_cpuid_fam_id(report.get()), + tav_snp_attestation_report_cpuid_mod_id(report.get())); + if (product == ProductName::Milan || product == ProductName::Genoa) + { + return chip_id; + } + if (product == ProductName::Turin) + { + return chip_id.first(8); + } + throw std::logic_error( + fmt::format("Unsupported SEV-SNP product: {}", product)); + } + + [[nodiscard]] AttestationReport parse_attestation_report_unverified( + std::span report); + static HostPort get_endpoint_loc( const EndorsementsServer& server, const HostPort& default_values) { @@ -475,24 +530,37 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== static EndorsementEndpointsConfiguration make_endorsement_endpoint_configuration( - const Attestation& quote, + const AttestationReport& quote, const snp::EndorsementsServers& endorsements_servers = {}) { - if (quote.version < minimum_attestation_version) + if (quote == nullptr) + { + throw std::logic_error("Cannot access an empty SNP attestation report"); + } + if ( + tav_snp_attestation_report_version(quote.get()) < + minimum_attestation_version) { throw std::logic_error(fmt::format( "SEV-SNP: attestation version {} is not supported. Minimum " "supported version is {}", - quote.version, + tav_snp_attestation_report_version(quote.get()), minimum_attestation_version)); } EndorsementEndpointsConfiguration config; auto chip_id_hex = - fmt::format("{:02x}", fmt::join(quote.get_chip_id_for_vcek(), "")); + fmt::format("{:02x}", fmt::join(get_chip_id_for_vcek(quote), "")); + const uint8_t* reported_tcb_data = nullptr; + size_t reported_tcb_size = 0; + tav_snp_attestation_report_reported_tcb( + quote.get(), &reported_tcb_data, &reported_tcb_size); + const auto reported_tcb_raw = + std::span{reported_tcb_data, reported_tcb_size}; auto reported_tcb = fmt::format( - "{:0x}", *reinterpret_cast("e.reported_tcb)); + "{:02x}", + fmt::join(reported_tcb_raw.rbegin(), reported_tcb_raw.rend(), "")); constexpr size_t default_max_retries_count = 10; static const ds::SizeString default_max_client_response_size = @@ -533,8 +601,9 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== } case EndorsementsEndpointType::AMD: { - auto product = - get_sev_snp_product(quote.cpuid_fam_id, quote.cpuid_mod_id); + auto product = get_sev_snp_product( + tav_snp_attestation_report_cpuid_fam_id(quote.get()), + tav_snp_attestation_report_cpuid_mod_id(quote.get())); std::string boot_loader; std::string tee; @@ -546,7 +615,9 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== case ProductName::Milan: case ProductName::Genoa: { - auto tcb = quote.reported_tcb.to_policy(product).to_milan_genoa(); + auto tcb = TcbVersionRaw(reported_tcb_raw) + .to_policy(product) + .to_milan_genoa(); boot_loader = fmt::format("{}", tcb.boot_loader); tee = fmt::format("{}", tcb.tee); snp = fmt::format("{}", tcb.snp); @@ -555,7 +626,8 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== } case ProductName::Turin: { - auto tcb = quote.reported_tcb.to_policy(product).to_turin(); + auto tcb = + TcbVersionRaw(reported_tcb_raw).to_policy(product).to_turin(); boot_loader = fmt::format("{}", tcb.boot_loader); tee = fmt::format("{}", tcb.tee); snp = fmt::format("{}", tcb.snp); @@ -608,14 +680,33 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== return config; } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + [[deprecated("Use the AttestationReport overload")]] + static EndorsementEndpointsConfiguration + make_endorsement_endpoint_configuration( + const Attestation& quote, + const snp::EndorsementsServers& endorsements_servers = {}) + { + const auto* report = reinterpret_cast("e); + return make_endorsement_endpoint_configuration( + parse_attestation_report_unverified({report, attestation_report_size}), + endorsements_servers); + } + class AttestationInterface { public: - [[nodiscard]] virtual const snp::Attestation& get() const = 0; + [[deprecated( + "Use get_raw() and explicitly decode with " + "parse_attestation_report_unverified")]] [[nodiscard]] virtual const snp:: + Attestation& + get() const = 0; virtual std::vector get_raw() = 0; virtual ~AttestationInterface() = default; }; +#pragma GCC diagnostic pop } diff --git a/include/ccf/pal/snp_ioctl.h b/include/ccf/pal/snp_ioctl.h index 445a9b9a210f..7a23eee47c46 100644 --- a/include/ccf/pal/snp_ioctl.h +++ b/include/ccf/pal/snp_ioctl.h @@ -11,6 +11,8 @@ namespace ccf::pal::snp return ioctl6::supports_sev_snp(); } + // Acquire an attestation object. get_raw() returns owned, unverified bytes; + // decode them explicitly with parse_attestation_report_unverified(). static std::unique_ptr get_attestation( const PlatformAttestationReportData& report_data) { diff --git a/include/ccf/pal/snp_ioctl6.h b/include/ccf/pal/snp_ioctl6.h index 344febe028d4..0b6ffdce2f66 100644 --- a/include/ccf/pal/snp_ioctl6.h +++ b/include/ccf/pal/snp_ioctl6.h @@ -24,6 +24,31 @@ namespace ccf::pal::snp::ioctl6 { constexpr auto DEVICE = "/dev/sev-guest"; + namespace detail + { + // Linux snp_guest_msg is 4096 bytes: a 96-byte outer message header and + // 4000-byte payload. The ioctl returns only the decrypted payload, with + // its own 32-byte report response header before the report. + // https://github.com/torvalds/linux/blob/v6.8/drivers/virt/coco/sev-guest/sev-guest.h + // https://github.com/torvalds/linux/blob/v6.8/include/uapi/linux/sev-guest.h + constexpr size_t ATTESTATION_RESPONSE_SIZE = 4000; + struct AttestationResponse + { + uint32_t status = 0; + uint32_t report_size = 0; + std::array reserved = {}; + std::array report_bytes = {}; + std::array< + uint8_t, + detail::ATTESTATION_RESPONSE_SIZE - 0x20 - attestation_report_size> + padding = {}; + }; + static_assert( + sizeof(AttestationResponse) == detail::ATTESTATION_RESPONSE_SIZE); + static_assert(offsetof(AttestationResponse, report_size) == 0x04); + static_assert(offsetof(AttestationResponse, report_bytes) == 0x20); + } + #pragma pack(push, 1) // Helper to add padding to a struct, so that the resulting struct has some // minimum size. As a minor detail, the padding will be initialised to 0. @@ -110,17 +135,24 @@ namespace ccf::pal::snp::ioctl6 #pragma pack(pop) // Table 25 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" #pragma pack(push, 1) struct AttestationResp { uint32_t status = 0; uint32_t report_size = 0; uint8_t reserved[0x20 - 0x8] = {0}; - Attestation report; + [[deprecated("Use get_raw() and parse_attestation_report_unverified")]] + snp::Attestation report = {}; uint8_t padding[64] = {0}; // padding to the size of SEV_SNP_REPORT_RSP_BUF_SZ (i.e., 1280 bytes) }; #pragma pack(pop) + static_assert(offsetof(AttestationResp, report_size) == 0x04); + static_assert(offsetof(AttestationResp, report) == 0x20); + static_assert(sizeof(AttestationResp) == 1280); +#pragma GCC diagnostic pop // Table 20 of the SEVSNP ABI constexpr uint8_t GUEST_FIELD_SELECT_GUEST_POLICY = 0b00000001; @@ -193,10 +225,25 @@ namespace ccf::pal::snp::ioctl6 using GuestRequestDerivedKey = GuestRequest; + namespace detail + { + using AttestationRequest = + GuestRequest; + static_assert( + sizeof(AttestationRequest) == sizeof(GuestRequestAttestation)); + static_assert(offsetof(AttestationRequest, req_data) == 8); + static_assert(offsetof(AttestationRequest, resp_wrapper) == 16); + static_assert(offsetof(AttestationRequest, exit_info) == 24); + static_assert(sizeof(AttestationRequest) == 32); + } + // From linux/include/uapi/linux/sev-guest.h constexpr char SEV_GUEST_IOC_TYPE = 'S'; constexpr int SEV_SNP_GUEST_MSG_REPORT = - _IOWR(SEV_GUEST_IOC_TYPE, 0x0, GuestRequestAttestation); + _IOWR(SEV_GUEST_IOC_TYPE, 0x0, detail::AttestationRequest); + static_assert( + _IOWR(SEV_GUEST_IOC_TYPE, 0x0, detail::AttestationRequest) == + _IOWR(SEV_GUEST_IOC_TYPE, 0x0, GuestRequestAttestation)); constexpr int SEV_SNP_GUEST_MSG_DERIVED_KEY = _IOWR(SEV_GUEST_IOC_TYPE, 0x1, GuestRequestDerivedKey); @@ -205,13 +252,11 @@ namespace ccf::pal::snp::ioctl6 return access(DEVICE, W_OK) == 0; } - class Attestation : public AttestationInterface + namespace detail { - IoctlSentinel resp_with_sentinel; - PaddedAttestationResp& padded_resp = resp_with_sentinel.data; - - public: - Attestation(const PlatformAttestationReportData& report_data) + inline void request_attestation( + const PlatformAttestationReportData& report_data, + IoctlSentinel& response) { AttestationReq req = {}; if (report_data.data.size() <= snp_attestation_report_data_size) @@ -235,8 +280,8 @@ namespace ccf::pal::snp::ioctl6 // Documented at // https://www.kernel.org/doc/html/latest/virt/coco/sev-guest.html - GuestRequestAttestation payload = { - .req_data = &req, .resp_wrapper = &padded_resp, .exit_info = {0}}; + AttestationRequest payload = { + .req_data = &req, .resp_wrapper = &response.data, .exit_info = {0}}; int rc = ioctl(fd, SEV_SNP_GUEST_MSG_REPORT, &payload); if (rc < 0) @@ -250,7 +295,7 @@ namespace ccf::pal::snp::ioctl6 throw std::logic_error(msg); } - if (!resp_with_sentinel.sentinels_intact()) + if (!response.sentinels_intact()) { // This occurs if a kernel/firmware upgrade causes the response to // overflow our struct. If that happens, it is better to fail early than @@ -259,17 +304,54 @@ namespace ccf::pal::snp::ioctl6 "SEV_SNP_GUEST_MSG_REPORT IOCTL overwrote safety sentinels."); } } + } + + class Attestation : public AttestationInterface + { + PaddedAttestationResp padded_resp; + + public: + Attestation(const PlatformAttestationReportData& report_data) + { + IoctlSentinel response; + detail::request_attestation(report_data, response); + // Retain legacy storage for the reference returned by get(). + static_assert(sizeof(padded_resp) == sizeof(response.data)); + std::memcpy(&padded_resp, &response.data, sizeof(padded_resp)); + } - [[nodiscard]] const snp::Attestation& get() const override +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + [[deprecated( + "Use get_raw() and explicitly decode with " + "parse_attestation_report_unverified")]] [[nodiscard]] const ccf::pal:: + snp::Attestation& + get() const override { + if (padded_resp.report_size != attestation_report_size) + { + throw std::logic_error(fmt::format( + "Unexpected SEV-SNP attestation report size: {} != {}", + padded_resp.report_size, + attestation_report_size)); + } return padded_resp.report; } std::vector get_raw() override { - auto* quote_bytes = reinterpret_cast(&padded_resp.report); - return {quote_bytes, quote_bytes + padded_resp.report_size}; + if (padded_resp.report_size != attestation_report_size) + { + throw std::logic_error(fmt::format( + "Unexpected SEV-SNP attestation report size: {} != {}", + padded_resp.report_size, + attestation_report_size)); + } + const auto* report = + reinterpret_cast(&padded_resp.report); + return {report, report + attestation_report_size}; } +#pragma GCC diagnostic pop }; class DerivedKey diff --git a/src/js/extensions/snp_attestation.cpp b/src/js/extensions/snp_attestation.cpp index 245c2268410f..aa24eb15a898 100644 --- a/src/js/extensions/snp_attestation.cpp +++ b/src/js/extensions/snp_attestation.cpp @@ -22,9 +22,9 @@ namespace ccf::js::extensions { JSValue make_js_tcb_version( - js::core::Context& jsctx, pal::snp::TcbVersionRaw tcb) + js::core::Context& jsctx, std::span tcb) { - auto data_hex = jsctx.new_string(tcb.to_hex()); + auto data_hex = jsctx.new_string(pal::snp::TcbVersionRaw(tcb).to_hex()); JS_CHECK_EXC(data_hex); return data_hex.take(); } @@ -94,10 +94,9 @@ namespace ccf::js::extensions pal::PlatformAttestationMeasurement measurement = {}; pal::PlatformAttestationReportData report_data = {}; std::optional parsed_uvm_endorsements; - try { - pal::verify_snp_attestation_report( + const auto attestation = pal::verify_snp_attestation_report_and_get( quote_info, measurement, report_data); if (uvm_endorsements.has_value()) { @@ -107,223 +106,316 @@ namespace ccf::js::extensions measurement, default_uvm_roots_of_trust); } - } - catch (const std::exception& e) - { - return JS_ThrowRangeError(ctx, "%s", e.what()); - } - - auto attestation = *reinterpret_cast( - quote_info.quote.data()); - - auto r = jsctx.new_obj(); - JS_CHECK_EXC(r); - - auto a = jsctx.new_obj(); - JS_CHECK_EXC(a); - - JS_CHECK_SET(a.set_uint32("version", attestation.version)); - JS_CHECK_SET(a.set_uint32("guest_svn", attestation.guest_svn)); - - auto policy = jsctx.new_obj(); - JS_CHECK_EXC(policy); - - JS_CHECK_SET( - policy.set_uint32("abi_minor", attestation.policy.abi_minor)); - JS_CHECK_SET( - policy.set_uint32("abi_major", attestation.policy.abi_major)); - JS_CHECK_SET(policy.set_uint32("smt", attestation.policy.smt)); - JS_CHECK_SET( - policy.set_uint32("migrate_ma", attestation.policy.migrate_ma)); - JS_CHECK_SET(policy.set_uint32("debug", attestation.policy.debug)); - JS_CHECK_SET( - policy.set_uint32("single_socket", attestation.policy.single_socket)); + auto r = jsctx.new_obj(); + JS_CHECK_EXC(r); + + auto a = jsctx.new_obj(); + JS_CHECK_EXC(a); + + JS_CHECK_SET(a.set_uint32( + "version", tav_snp_attestation_report_version(attestation.get()))); + JS_CHECK_SET(a.set_uint32( + "guest_svn", + tav_snp_attestation_report_guest_svn(attestation.get()))); + + auto policy = jsctx.new_obj(); + JS_CHECK_EXC(policy); + + JS_CHECK_SET(policy.set_uint32( + "abi_minor", + tav_snp_attestation_report_policy_abi_minor(attestation.get()))); + JS_CHECK_SET(policy.set_uint32( + "abi_major", + tav_snp_attestation_report_policy_abi_major(attestation.get()))); + JS_CHECK_SET(policy.set_uint32( + "smt", tav_snp_attestation_report_policy_smt(attestation.get()))); + JS_CHECK_SET(policy.set_uint32( + "migrate_ma", + tav_snp_attestation_report_policy_migrate_ma(attestation.get()))); + JS_CHECK_SET(policy.set_uint32( + "debug", tav_snp_attestation_report_policy_debug(attestation.get()))); + JS_CHECK_SET(policy.set_uint32( + "single_socket", + tav_snp_attestation_report_policy_single_socket(attestation.get()))); + + JS_CHECK_SET(a.set("policy", std::move(policy))); - JS_CHECK_SET(a.set("policy", std::move(policy))); - - { - auto family_id = jsctx.new_array_buffer_copy(attestation.family_id); - JS_CHECK_EXC(family_id); - JS_CHECK_SET(a.set("family_id", std::move(family_id))); - } - - { - auto image_id = jsctx.new_array_buffer_copy(attestation.image_id); - JS_CHECK_EXC(image_id); - JS_CHECK_SET(a.set("image_id", std::move(image_id))); - } - - JS_CHECK_SET(a.set_uint32("vmpl", attestation.vmpl)); - JS_CHECK_SET(a.set_uint32( - "signature_algo", static_cast(attestation.signature_algo))); - - { - auto platform_version = - jsctx.wrap(make_js_tcb_version(jsctx, attestation.platform_version)); - JS_CHECK_EXC(platform_version); - JS_CHECK_SET(a.set("platform_version", std::move(platform_version))); - } - - { - auto platform_info = jsctx.new_obj(); - JS_CHECK_EXC(platform_info); - JS_CHECK_SET( - platform_info.set_uint32("smt_en", attestation.platform_info.smt_en)); - JS_CHECK_SET(platform_info.set_uint32( - "tsme_en", attestation.platform_info.tsme_en)); - JS_CHECK_SET(a.set("plaform_info", std::move(platform_info))); - } + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_family_id(attestation.get(), &data, &size); + auto family_id = + jsctx.new_array_buffer_copy(std::span{data, size}); + JS_CHECK_EXC(family_id); + JS_CHECK_SET(a.set("family_id", std::move(family_id))); + } - { - auto flags = jsctx.new_obj(); - JS_CHECK_EXC(flags); - JS_CHECK_SET( - flags.set_uint32("author_key_en", attestation.flags.author_key_en)); - JS_CHECK_SET( - flags.set_uint32("mask_chip_key", attestation.flags.mask_chip_key)); - JS_CHECK_SET( - flags.set_uint32("signing_key", attestation.flags.signing_key)); - JS_CHECK_SET(a.set("flags", std::move(flags))); - } + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_image_id(attestation.get(), &data, &size); + auto image_id = + jsctx.new_array_buffer_copy(std::span{data, size}); + JS_CHECK_EXC(image_id); + JS_CHECK_SET(a.set("image_id", std::move(image_id))); + } - { - auto attestation_report_data = - jsctx.new_array_buffer_copy(attestation.report_data); - JS_CHECK_EXC(attestation_report_data); - JS_CHECK_SET(a.set("report_data", std::move(attestation_report_data))); - } + JS_CHECK_SET(a.set_uint32( + "vmpl", tav_snp_attestation_report_vmpl(attestation.get()))); + JS_CHECK_SET(a.set_uint32( + "signature_algo", + static_cast( + tav_snp_attestation_report_signature_algo(attestation.get())))); - { - auto attestation_measurement = - jsctx.new_array_buffer_copy(attestation.measurement); - JS_CHECK_EXC(attestation_measurement); - JS_CHECK_SET(a.set("measurement", std::move(attestation_measurement))); - } + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_platform_version( + attestation.get(), &data, &size); + auto platform_version = jsctx.wrap( + make_js_tcb_version(jsctx, std::span{data, size})); + JS_CHECK_EXC(platform_version); + JS_CHECK_SET(a.set("platform_version", std::move(platform_version))); + } - { - auto attestation_host_data = - jsctx.new_array_buffer_copy(attestation.host_data); - JS_CHECK_EXC(attestation_host_data); - JS_CHECK_SET(a.set("host_data", std::move(attestation_host_data))); - } + { + auto platform_info = jsctx.new_obj(); + JS_CHECK_EXC(platform_info); + const auto raw_platform_info = + tav_snp_attestation_report_platform_info(attestation.get()); + JS_CHECK_SET( + platform_info.set_uint32("smt_en", raw_platform_info & 1)); + JS_CHECK_SET( + platform_info.set_uint32("tsme_en", (raw_platform_info >> 1) & 1)); + JS_CHECK_SET(a.set("plaform_info", std::move(platform_info))); + } - { - auto attestation_id_key_digest = - jsctx.new_array_buffer_copy(attestation.id_key_digest); - JS_CHECK_EXC(attestation_id_key_digest); - JS_CHECK_SET( - a.set("id_key_digest", std::move(attestation_id_key_digest))); - } + { + auto flags = jsctx.new_obj(); + JS_CHECK_EXC(flags); + JS_CHECK_SET(flags.set_uint32( + "author_key_en", + tav_snp_attestation_report_flags_author_key_en(attestation.get()))); + JS_CHECK_SET(flags.set_uint32( + "mask_chip_key", + tav_snp_attestation_report_flags_mask_chip_key(attestation.get()))); + JS_CHECK_SET(flags.set_uint32( + "signing_key", + tav_snp_attestation_report_flags_signing_key(attestation.get()))); + JS_CHECK_SET(a.set("flags", std::move(flags))); + } - { - auto attestation_author_key_digest = - jsctx.new_array_buffer_copy(attestation.author_key_digest); - JS_CHECK_EXC(attestation_author_key_digest); - JS_CHECK_SET( - a.set("author_key_digest", std::move(attestation_author_key_digest))); - } + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_report_data( + attestation.get(), &data, &size); + auto attestation_report_data = + jsctx.new_array_buffer_copy(std::span{data, size}); + JS_CHECK_EXC(attestation_report_data); + JS_CHECK_SET( + a.set("report_data", std::move(attestation_report_data))); + } - { - auto attestation_report_id = - jsctx.new_array_buffer_copy(attestation.report_id); - JS_CHECK_EXC(attestation_report_id); - JS_CHECK_SET(a.set("report_id", std::move(attestation_report_id))); - } + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_measurement( + attestation.get(), &data, &size); + auto attestation_measurement = + jsctx.new_array_buffer_copy(std::span{data, size}); + JS_CHECK_EXC(attestation_measurement); + JS_CHECK_SET( + a.set("measurement", std::move(attestation_measurement))); + } - { - auto attestation_report_id_ma = - jsctx.new_array_buffer_copy(attestation.report_id_ma); - JS_CHECK_EXC(attestation_report_id_ma); - JS_CHECK_SET( - a.set("report_id_ma", std::move(attestation_report_id_ma))); - } + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_host_data(attestation.get(), &data, &size); + auto attestation_host_data = + jsctx.new_array_buffer_copy(std::span{data, size}); + JS_CHECK_EXC(attestation_host_data); + JS_CHECK_SET(a.set("host_data", std::move(attestation_host_data))); + } - { - auto reported_tcb = - jsctx.wrap(make_js_tcb_version(jsctx, attestation.reported_tcb)); - JS_CHECK_EXC(reported_tcb); - JS_CHECK_SET(a.set("reported_tcb", std::move(reported_tcb))); - } + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_id_key_digest( + attestation.get(), &data, &size); + auto attestation_id_key_digest = + jsctx.new_array_buffer_copy(std::span{data, size}); + JS_CHECK_EXC(attestation_id_key_digest); + JS_CHECK_SET( + a.set("id_key_digest", std::move(attestation_id_key_digest))); + } - JS_CHECK_SET(a.set_uint32("cpuid_fam_id", attestation.cpuid_fam_id)); - JS_CHECK_SET(a.set_uint32("cpuid_mod_id", attestation.cpuid_mod_id)); - JS_CHECK_SET(a.set_uint32("cpuid_step", attestation.cpuid_step)); + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_author_key_digest( + attestation.get(), &data, &size); + auto attestation_author_key_digest = + jsctx.new_array_buffer_copy(std::span{data, size}); + JS_CHECK_EXC(attestation_author_key_digest); + JS_CHECK_SET(a.set( + "author_key_digest", std::move(attestation_author_key_digest))); + } - { - auto attestation_chip_id = - jsctx.new_array_buffer_copy(attestation.chip_id); - JS_CHECK_EXC(attestation_chip_id); - JS_CHECK_SET(a.set("chip_id", std::move(attestation_chip_id))); - } + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_report_id(attestation.get(), &data, &size); + auto attestation_report_id = + jsctx.new_array_buffer_copy(std::span{data, size}); + JS_CHECK_EXC(attestation_report_id); + JS_CHECK_SET(a.set("report_id", std::move(attestation_report_id))); + } - { - auto committed_tcb = - jsctx.wrap(make_js_tcb_version(jsctx, attestation.committed_tcb)); - JS_CHECK_EXC(committed_tcb); - JS_CHECK_SET(a.set("committed_tcb", std::move(committed_tcb))); - } + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_report_id_ma( + attestation.get(), &data, &size); + auto attestation_report_id_ma = + jsctx.new_array_buffer_copy(std::span{data, size}); + JS_CHECK_EXC(attestation_report_id_ma); + JS_CHECK_SET( + a.set("report_id_ma", std::move(attestation_report_id_ma))); + } - JS_CHECK_SET(a.set_uint32("current_minor", attestation.current_minor)); - JS_CHECK_SET(a.set_uint32("current_build", attestation.current_build)); - JS_CHECK_SET(a.set_uint32("current_major", attestation.current_major)); - JS_CHECK_SET( - a.set_uint32("committed_build", attestation.committed_build)); - JS_CHECK_SET( - a.set_uint32("committed_minor", attestation.committed_minor)); - JS_CHECK_SET( - a.set_uint32("committed_major", attestation.committed_major)); + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_reported_tcb( + attestation.get(), &data, &size); + auto reported_tcb = jsctx.wrap( + make_js_tcb_version(jsctx, std::span{data, size})); + JS_CHECK_EXC(reported_tcb); + JS_CHECK_SET(a.set("reported_tcb", std::move(reported_tcb))); + } - { - auto launch_tcb = - jsctx.wrap(make_js_tcb_version(jsctx, attestation.launch_tcb)); - JS_CHECK_EXC(launch_tcb); - JS_CHECK_SET(a.set("launch_tcb", std::move(launch_tcb))); - } + JS_CHECK_SET(a.set_uint32( + "cpuid_fam_id", + tav_snp_attestation_report_cpuid_fam_id(attestation.get()))); + JS_CHECK_SET(a.set_uint32( + "cpuid_mod_id", + tav_snp_attestation_report_cpuid_mod_id(attestation.get()))); + JS_CHECK_SET(a.set_uint32( + "cpuid_step", + tav_snp_attestation_report_cpuid_step(attestation.get()))); - auto signature = jsctx.new_obj(); - JS_CHECK_EXC(signature); + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_chip_id(attestation.get(), &data, &size); + auto attestation_chip_id = + jsctx.new_array_buffer_copy(std::span{data, size}); + JS_CHECK_EXC(attestation_chip_id); + JS_CHECK_SET(a.set("chip_id", std::move(attestation_chip_id))); + } - { - auto signature_r = jsctx.new_array_buffer_copy(attestation.signature.r); - JS_CHECK_EXC(signature_r); - JS_CHECK_SET(signature.set("r", std::move(signature_r))); - } + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_committed_tcb( + attestation.get(), &data, &size); + auto committed_tcb = jsctx.wrap( + make_js_tcb_version(jsctx, std::span{data, size})); + JS_CHECK_EXC(committed_tcb); + JS_CHECK_SET(a.set("committed_tcb", std::move(committed_tcb))); + } - { - auto signature_s = jsctx.new_array_buffer_copy(attestation.signature.s); - JS_CHECK_EXC(signature_s); - JS_CHECK_SET(signature.set("s", std::move(signature_s))); - } + JS_CHECK_SET(a.set_uint32( + "current_minor", + tav_snp_attestation_report_current_minor(attestation.get()))); + JS_CHECK_SET(a.set_uint32( + "current_build", + tav_snp_attestation_report_current_build(attestation.get()))); + JS_CHECK_SET(a.set_uint32( + "current_major", + tav_snp_attestation_report_current_major(attestation.get()))); + JS_CHECK_SET(a.set_uint32( + "committed_build", + tav_snp_attestation_report_committed_build(attestation.get()))); + JS_CHECK_SET(a.set_uint32( + "committed_minor", + tav_snp_attestation_report_committed_minor(attestation.get()))); + JS_CHECK_SET(a.set_uint32( + "committed_major", + tav_snp_attestation_report_committed_major(attestation.get()))); - JS_CHECK_SET(a.set("signature", std::move(signature))); - JS_CHECK_SET(r.set("attestation", std::move(a))); + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_launch_tcb( + attestation.get(), &data, &size); + auto launch_tcb = jsctx.wrap( + make_js_tcb_version(jsctx, std::span{data, size})); + JS_CHECK_EXC(launch_tcb); + JS_CHECK_SET(a.set("launch_tcb", std::move(launch_tcb))); + } - if (parsed_uvm_endorsements.has_value()) - { - auto u = jsctx.new_obj(); - JS_CHECK_EXC(u); + auto signature = jsctx.new_obj(); + JS_CHECK_EXC(signature); { - auto did = jsctx.new_string(parsed_uvm_endorsements.value().did); - JS_CHECK_EXC(did); - JS_CHECK_SET(u.set("did", std::move(did))); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_signature_r( + attestation.get(), &data, &size); + auto signature_r = + jsctx.new_array_buffer_copy(std::span{data, size}); + JS_CHECK_EXC(signature_r); + JS_CHECK_SET(signature.set("r", std::move(signature_r))); } { - auto feed = jsctx.new_string(parsed_uvm_endorsements.value().feed); - JS_CHECK_EXC(feed); - JS_CHECK_SET(u.set("feed", std::move(feed))); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_signature_s( + attestation.get(), &data, &size); + auto signature_s = + jsctx.new_array_buffer_copy(std::span{data, size}); + JS_CHECK_EXC(signature_s); + JS_CHECK_SET(signature.set("s", std::move(signature_s))); } + JS_CHECK_SET(a.set("signature", std::move(signature))); + JS_CHECK_SET(r.set("attestation", std::move(a))); + + if (parsed_uvm_endorsements.has_value()) { - auto svn = jsctx.new_string(parsed_uvm_endorsements.value().svn); - JS_CHECK_EXC(svn); - JS_CHECK_SET(u.set("svn", std::move(svn))); - JS_CHECK_SET(r.set("uvm_endorsements", std::move(u))); + auto u = jsctx.new_obj(); + JS_CHECK_EXC(u); + + { + auto did = jsctx.new_string(parsed_uvm_endorsements.value().did); + JS_CHECK_EXC(did); + JS_CHECK_SET(u.set("did", std::move(did))); + } + + { + auto feed = jsctx.new_string(parsed_uvm_endorsements.value().feed); + JS_CHECK_EXC(feed); + JS_CHECK_SET(u.set("feed", std::move(feed))); + } + + { + auto svn = jsctx.new_string(parsed_uvm_endorsements.value().svn); + JS_CHECK_EXC(svn); + JS_CHECK_SET(u.set("svn", std::move(svn))); + JS_CHECK_SET(r.set("uvm_endorsements", std::move(u))); + } } - } - return r.take(); + return r.take(); + } + catch (const std::exception& e) + { + return JS_ThrowRangeError(ctx, "%s", e.what()); + } } #pragma clang diagnostic pop diff --git a/src/node/node_state.h b/src/node/node_state.h index c60aa82d8a79..9c91c512e4c1 100644 --- a/src/node/node_state.h +++ b/src/node/node_state.h @@ -881,10 +881,14 @@ namespace ccf } auto snp_attestation = - AttestationProvider::get_snp_attestation(quote_info); + AttestationProvider::get_snp_attestation_report(quote_info); if (snp_attestation.has_value()) { - snp_tcb_version = snp_attestation.value().reported_tcb; + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_reported_tcb( + snp_attestation.value().get(), &data, &size); + snp_tcb_version = ccf::pal::snp::TcbVersionRaw({data, size}); } // Verify that the security policy matches the quoted digest of the policy @@ -1032,19 +1036,18 @@ namespace ccf // Check that tcbm in endorsement matches reported TCB in our // retrieved attestation - const auto* quote = - reinterpret_cast( - quote_info.quote.data()); - const auto reported_tcb = quote->reported_tcb; - - // tcbm is a single hex value, like DB18000000000004. To match - // that with a TcbVersion, reverse the bytes. - const auto* tcb_begin = - reinterpret_cast(&reported_tcb); - const std::span tcb_bytes{ - tcb_begin, tcb_begin + sizeof(reported_tcb)}; - auto tcb_as_hex = fmt::format( - "{:02x}", fmt::join(tcb_bytes.rbegin(), tcb_bytes.rend(), "")); + const auto report = + ccf::pal::snp::parse_attestation_report_unverified( + quote_info.quote); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_reported_tcb( + report.get(), &data, &size); + const auto reported_tcb = + ccf::pal::snp::TcbVersionRaw({data, size}); + + // tcbm is a single hex value, like DB18000000000004. + auto tcb_as_hex = reported_tcb.to_hex(); ccf::nonstd::to_upper(tcb_as_hex); if (tcb_as_hex == aci_endorsements.tcbm) diff --git a/src/node/quote.cpp b/src/node/quote.cpp index 03ef0db0b30e..9606d60571b9 100644 --- a/src/node/quote.cpp +++ b/src/node/quote.cpp @@ -22,6 +22,7 @@ #include "node/js_policy.h" #include "node/uvm_endorsements.h" +#include #include namespace ccf @@ -153,8 +154,8 @@ namespace ccf return measurement; } - std::optional AttestationProvider::get_snp_attestation( - const QuoteInfo& quote_info) + std::optional AttestationProvider:: + get_snp_attestation_report(const QuoteInfo& quote_info) { if (quote_info.format != QuoteFormat::amd_sev_snp_v1) { @@ -164,10 +165,7 @@ namespace ccf { pal::PlatformAttestationMeasurement d = {}; pal::PlatformAttestationReportData r = {}; - pal::verify_quote(quote_info, d, r); - auto attestation = *reinterpret_cast( - quote_info.quote.data()); - return attestation; + return pal::verify_snp_attestation_report_and_get(quote_info, d, r); } catch (const std::exception& e) { @@ -176,6 +174,33 @@ namespace ccf } } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + std::optional AttestationProvider::get_snp_attestation( + const QuoteInfo& quote_info) + { + auto report = get_snp_attestation_report(quote_info); + if (!report.has_value()) + { + return std::nullopt; + } + + if (quote_info.quote.size() != sizeof(pal::snp::Attestation)) + { + LOG_FAIL_FMT( + "Verified SNP report has unexpected size {} (expected {})", + quote_info.quote.size(), + sizeof(pal::snp::Attestation)); + return std::nullopt; + } + + pal::snp::Attestation legacy_report = {}; + std::memcpy( + &legacy_report, quote_info.quote.data(), sizeof(pal::snp::Attestation)); + return legacy_report; + } +#pragma GCC diagnostic pop + std::optional AttestationProvider::get_host_data( const QuoteInfo& quote_info) { @@ -205,13 +230,13 @@ namespace ccf pal::PlatformAttestationReportData r = {}; try { - pal::verify_quote(quote_info, d, r); - auto quote = *reinterpret_cast( - quote_info.quote.data()); - std::copy( - std::begin(quote.host_data), - std::end(quote.host_data), - rep.begin()); + const auto report = + pal::verify_snp_attestation_report_and_get(quote_info, d, r); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_host_data(report.get(), &data, &size); + const auto host_data = std::span{data, size}; + std::copy(host_data.begin(), host_data.end(), rep.begin()); } catch (const std::exception& e) { @@ -279,9 +304,8 @@ namespace ccf pal::PlatformAttestationMeasurement d = {}; pal::PlatformAttestationReportData r = {}; - pal::verify_quote(quote_info, d, r); auto attestation = - *reinterpret_cast(quote_info.quote.data()); + pal::verify_snp_attestation_report_and_get(quote_info, d, r); std::optional min_tcb_opt = std::nullopt; auto* h = tx.ro(Tables::SNP_TCB_VERSIONS); @@ -290,9 +314,12 @@ namespace ccf const std::string& cpuid_hex, const pal::snp::TcbVersionPolicy& v) { auto cpuid = pal::snp::cpuid_from_hex(cpuid_hex); if ( - cpuid.get_family_id() == attestation.cpuid_fam_id && - cpuid.get_model_id() == attestation.cpuid_mod_id && - cpuid.stepping == attestation.cpuid_step) + cpuid.get_family_id() == + tav_snp_attestation_report_cpuid_fam_id(attestation.get()) && + cpuid.get_model_id() == + tav_snp_attestation_report_cpuid_mod_id(attestation.get()) && + cpuid.stepping == + tav_snp_attestation_report_cpuid_step(attestation.get())) { min_tcb_opt = v; return false; @@ -307,9 +334,13 @@ namespace ccf // CPUID of the attested cpu must now be equal to the min_tcb_opt's cpuid auto product_family = pal::snp::get_sev_snp_product( - attestation.cpuid_fam_id, attestation.cpuid_mod_id); + tav_snp_attestation_report_cpuid_fam_id(attestation.get()), + tav_snp_attestation_report_cpuid_mod_id(attestation.get())); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_reported_tcb(attestation.get(), &data, &size); auto attestation_tcb_policy = - attestation.reported_tcb.to_policy(product_family); + pal::snp::TcbVersionRaw({data, size}).to_policy(product_family); if (pal::snp::TcbVersionPolicy::is_valid( min_tcb_opt.value(), attestation_tcb_policy)) diff --git a/src/node/rpc/node_frontend.h b/src/node/rpc/node_frontend.h index ae596eb13906..e51ebc0e3eb6 100644 --- a/src/node/rpc/node_frontend.h +++ b/src/node/rpc/node_frontend.h @@ -1662,7 +1662,8 @@ namespace ccf ctx.tx, in.snp_uvm_endorsements, recovering); auto attestation = - AttestationProvider::get_snp_attestation(in.quote_info).value(); + AttestationProvider::get_snp_attestation_report(in.quote_info) + .value(); InternalTablesAccess::trust_node_snp_tcb_version( ctx.tx, attestation); break; diff --git a/src/node/rpc/test/internal_tables_access_test.cpp b/src/node/rpc/test/internal_tables_access_test.cpp index 42b90fed27e5..486e0e22e00c 100644 --- a/src/node/rpc/test/internal_tables_access_test.cpp +++ b/src/node/rpc/test/internal_tables_access_test.cpp @@ -89,6 +89,17 @@ TEST_CASE("direct node deletion updates consensus configuration") REQUIRE(consensus.configuration_changes == 1); } +TEST_CASE("trust_node_snp_tcb_version rejects an empty owner") +{ + ccf::kv::Store kv_store; + auto tx = kv_store.create_tx(); + const pal::snp::AttestationReport report; + CHECK_THROWS_WITH_AS( + InternalTablesAccess::trust_node_snp_tcb_version(tx, report), + "Cannot access an empty SNP attestation report", + std::logic_error); +} + TEST_CASE("trust_node_uvm_endorsements - not recovering, empty map") { ccf::kv::Store kv_store; diff --git a/src/pal/attestation.cpp b/src/pal/attestation.cpp index 8df0a1fbea34..29f1e7167c37 100644 --- a/src/pal/attestation.cpp +++ b/src/pal/attestation.cpp @@ -3,9 +3,7 @@ #include "ccf/pal/attestation.h" -#include "ccf/crypto/ecdsa.h" #include "ccf/crypto/openssl/openssl_wrappers.h" -#include "ccf/crypto/verifier.h" #include "ccf/ds/json.h" #include "ccf/pal/attestation_sev_snp.h" #include "ccf/pal/sev_snp_cpuid.h" @@ -16,37 +14,49 @@ namespace ccf::pal { - using Unique_ASN1_OBJECT = ccf::crypto::OpenSSL:: - Unique_SSL_OBJECT; - using Unique_ASN1_INTEGER = ccf::crypto::OpenSSL:: - Unique_SSL_OBJECT; - namespace { - std::string x509_name_to_rfc2253_string(X509_NAME* name) - { - ccf::crypto::OpenSSL::CHECKNULL(name); + using TavErrorPtr = std::unique_ptr; - ccf::crypto::OpenSSL::Unique_BIO mem; - const auto rc = X509_NAME_print_ex(mem, name, 0, XN_FLAG_RFC2253); - if (rc < 0) + void check_tav_error(std::string_view operation, const TavError* error) + { + if (error != nullptr) { - const auto ec = ERR_get_error(); - throw std::runtime_error(fmt::format( - "OpenSSL error (rc={}, ec={}): {}", - rc, - ec, - ccf::crypto::OpenSSL::error_string(ec))); + throw std::logic_error(fmt::format( + "SEV-SNP: TAV {} failed ({}): {}", + operation, + static_cast(tav_error_code(error)), + tav_error_message(error))); } + } + } - BUF_MEM* bptr = nullptr; - ccf::crypto::OpenSSL::CHECK1(BIO_get_mem_ptr(mem, &bptr)); - ccf::crypto::OpenSSL::CHECKNULL(bptr); - - return {bptr->data, bptr->length}; + namespace snp + { + AttestationReport parse_attestation_report_unverified( + std::span report) + { + TavSnpAttestationReport* raw_report = nullptr; + TavErrorPtr error( + tav_snp_attestation_report_from_unverified_bytes( + report.data(), report.size(), &raw_report), + tav_error_free); + AttestationReport parsed_report(raw_report); + check_tav_error("unverified report parsing", error.get()); + if (parsed_report == nullptr) + { + throw std::logic_error( + "SEV-SNP: TAV parsing succeeded without returning a report"); + } + return parsed_report; } } + using Unique_ASN1_OBJECT = ccf::crypto::OpenSSL:: + Unique_SSL_OBJECT; + using Unique_ASN1_INTEGER = ccf::crypto::OpenSSL:: + Unique_SSL_OBJECT; + void verify_virtual_attestation_report( const QuoteInfo& quote_info, PlatformAttestationMeasurement& measurement, @@ -235,7 +245,7 @@ namespace ccf::pal } // Verifying SNP attestation report is available on all platforms. - void verify_snp_attestation_report( + snp::AttestationReport verify_snp_attestation_report_and_get( const QuoteInfo& quote_info, PlatformAttestationMeasurement& measurement, PlatformAttestationReportData& report_data) @@ -247,33 +257,20 @@ namespace ccf::pal quote_info.format)); } - if (quote_info.quote.size() != sizeof(snp::Attestation)) + const auto& report = quote_info.quote; + const auto& endorsements = quote_info.endorsements; + if (report.size() != snp::attestation_report_size) { throw std::logic_error(fmt::format( "Input SEV-SNP attestation report is not of expected size {}: {}", - sizeof(snp::Attestation), - quote_info.quote.size())); + snp::attestation_report_size, + report.size())); } - auto quote = - *reinterpret_cast(quote_info.quote.data()); - - if (quote.version < snp::minimum_attestation_version) - { - throw std::logic_error(fmt::format( - "SEV-SNP: Attestation version is {} not >= expected minimum {}", - quote.version, - snp::minimum_attestation_version)); - } - - auto product_family = - snp::get_sev_snp_product(quote.cpuid_fam_id, quote.cpuid_mod_id); - // ---- Verify certificate chain ---- auto certificates = ccf::crypto::split_x509_cert_bundle(std::string_view( - reinterpret_cast(quote_info.endorsements.data()), - quote_info.endorsements.size())); + reinterpret_cast(endorsements.data()), endorsements.size())); if (certificates.size() != 3) { throw std::logic_error(fmt::format( @@ -286,97 +283,54 @@ namespace ccf::pal auto ask_cert = certificates[1]; auto ark_cert = certificates[2]; - auto ark_verifier = ccf::crypto::make_verifier(ark_cert); - - auto key = snp::amd_root_signing_keys.find(product_family); - if (key == snp::amd_root_signing_keys.end()) - { - throw std::logic_error(fmt::format( - "SEV-SNP: No known root certificate for {}", product_family)); - } - const auto& expected_ark = key->second; - if (ark_verifier->public_key_pem().str() != expected_ark.public_key) - { - throw std::logic_error(fmt::format( - "SEV-SNP: The root of trust public key for this attestation was not " - "the expected one for v{} {} {}: {} != {}", - quote.version, - quote.cpuid_fam_id, - quote.cpuid_mod_id, - ark_verifier->public_key_pem().str(), - expected_ark.public_key)); - } - - ccf::crypto::OpenSSL::Unique_BIO mem_bio(ark_cert); - ccf::crypto::OpenSSL::Unique_X509 x509( - mem_bio, true, true /* check_null */); - const auto issuer = x509_name_to_rfc2253_string(X509_get_issuer_name(x509)); - if (issuer != expected_ark.issuer) - { - throw std::logic_error(fmt::format( - "SEV-SNP: The root of trust issuer for this attestation was not " - "the expected one for {}: {} != {}", - product_family, - issuer, - expected_ark.issuer)); - } - - if (!ark_verifier->verify_certificate({&ark_cert})) - { - throw std::logic_error( - "SEV-SNP: The root of trust public key for this attestation was not " - "self signed as expected"); - } - - auto vcek_verifier = ccf::crypto::make_verifier(/* leaf */ vcek_cert); - if (!vcek_verifier->verify_certificate( - /* root */ {&ark_cert}, /* chain */ {&ask_cert})) + TavSnpAttestationReport* raw_report = nullptr; + TavErrorPtr error( + tav_verify_snp_attestation( + report.data(), + report.size(), + ark_cert.data(), + ark_cert.size(), + ask_cert.data(), + ask_cert.size(), + vcek_cert.data(), + vcek_cert.size(), + &raw_report), + tav_error_free); + snp::AttestationReport attestation(raw_report); + check_tav_error("verification", error.get()); + if (attestation == nullptr) { throw std::logic_error( - "SEV-SNP: The chain of signatures from the root of trust to this " - "attestation is broken"); + "SEV-SNP: TAV verification succeeded without returning a report"); } - // ---- Verify attestation report signature ---- - - // According to Table 134 (2025-06-12) only ecdsa_p384_sha384 is supported - if (quote.signature_algo != snp::SignatureAlgorithm::ecdsa_p384_sha384) + if ( + tav_snp_attestation_report_version(attestation.get()) < + snp::minimum_attestation_version) { throw std::logic_error(fmt::format( - "SEV-SNP: Unsupported signature algorithm: {} (supported: {})", - quote.signature_algo, - snp::SignatureAlgorithm::ecdsa_p384_sha384)); + "SEV-SNP: Attestation version is {} not >= expected minimum {}", + tav_snp_attestation_report_version(attestation.get()), + snp::minimum_attestation_version)); } - // Make ASN1 DER signature - auto quote_signature = ccf::crypto::ecdsa_sig_from_r_s( - quote.signature.r, - sizeof(quote.signature.r), - quote.signature.s, - sizeof(quote.signature.s), - false /* little endian */ - ); - - std::span quote_without_signature{ - quote_info.quote.data(), - quote_info.quote.size() - sizeof(quote.signature)}; - if (!vcek_verifier->verify(quote_without_signature, quote_signature)) - { - throw std::logic_error( - "SEV-SNP: Chip certificate (VCEK) did not sign this attestation"); - } + const auto product_family = snp::get_sev_snp_product( + tav_snp_attestation_report_cpuid_fam_id(attestation.get()), + tav_snp_attestation_report_cpuid_mod_id(attestation.get())); // ---- Verify attestation report contents ---- - if (quote.flags.signing_key != snp::attestation_flags_signing_key_vcek) + if ( + tav_snp_attestation_report_flags_signing_key(attestation.get()) != + snp::attestation_flags_signing_key_vcek) { throw std::logic_error(fmt::format( "SEV-SNP: Attestation report must be signed by VCEK: {}", - static_cast(quote.flags.signing_key))); + tav_snp_attestation_report_flags_signing_key(attestation.get()))); } // mask_chip_key if set means the operator set the vcek to 0s - if (quote.flags.mask_chip_key != 0) + if (tav_snp_attestation_report_flags_mask_chip_key(attestation.get())) { throw std::logic_error( fmt::format("SEV-SNP: Mask chip key must not be set")); @@ -385,15 +339,15 @@ namespace ccf::pal // All attestation reports generated by guests must have VMPL <= 3 // while host generated reports have VMPL > 3. // We should reject host generated reports. - if (quote.vmpl > 3) + if (tav_snp_attestation_report_vmpl(attestation.get()) > 3) { throw std::logic_error(fmt::format( "SEV-SNP: This report seems to be host generated (VMPL {} > 3)", - quote.vmpl)); + tav_snp_attestation_report_vmpl(attestation.get()))); } // Debug mode would allow decryption of guest pages - if (quote.policy.debug != 0) + if (tav_snp_attestation_report_policy_debug(attestation.get())) { throw std::logic_error( "SEV-SNP: SNP attestation report guest policy debugging must not be " @@ -402,18 +356,25 @@ namespace ccf::pal // Migration of CCF nodes and other services could allow duplicates, and // hence must be disallowed - if (quote.policy.migrate_ma != 0) + if (tav_snp_attestation_report_policy_migrate_ma(attestation.get())) { throw std::logic_error( "SEV-SNP: SNP attestation report guest policy migration must not be " "enabled"); } + const uint8_t* reported_tcb_data = nullptr; + size_t reported_tcb_size = 0; + tav_snp_attestation_report_reported_tcb( + attestation.get(), &reported_tcb_data, &reported_tcb_size); + const auto reported_tcb_raw = + std::span{reported_tcb_data, reported_tcb_size}; auto endorsed_tcb = get_endorsed_tcb_from_cert(product_family, vcek_cert); if (endorsed_tcb.has_value()) { auto endorsed_tcb_policy = endorsed_tcb->to_policy(product_family); - auto reported_tcb = quote.reported_tcb.to_policy(product_family); + auto reported_tcb = + snp::TcbVersionRaw(reported_tcb_raw).to_policy(product_family); if (!snp::TcbVersionPolicy::is_valid(endorsed_tcb_policy, reported_tcb)) { @@ -426,7 +387,7 @@ namespace ccf::pal } auto endorsed_chip_id = get_endorsed_chip_id_from_cert(vcek_cert); - auto reported_chip_id = quote.get_chip_id_for_vcek(); + auto reported_chip_id = snp::get_chip_id_for_vcek(attestation); if ( endorsed_chip_id.has_value() && (endorsed_chip_id->size() != reported_chip_id.size() || @@ -444,13 +405,14 @@ namespace ccf::pal if (quote_info.endorsed_tcb.has_value()) { - const auto& quote_endorsed_tcb = quote_info.endorsed_tcb.value(); - auto raw_endorsed_tcb = snp::TcbVersionRaw::from_hex(quote_endorsed_tcb); + auto raw_endorsed_tcb = + snp::TcbVersionRaw::from_hex(quote_info.endorsed_tcb.value()); - if (raw_endorsed_tcb != quote.reported_tcb) + const auto reported_tcb = snp::TcbVersionRaw(reported_tcb_raw); + if (raw_endorsed_tcb != reported_tcb) { auto endorsed_tcb_hex = raw_endorsed_tcb.to_hex(); - auto report_tcb_hex = quote.reported_tcb.to_hex(); + auto report_tcb_hex = reported_tcb.to_hex(); throw std::logic_error(fmt::format( "SEV-SNP: endorsed TCB {} does not match reported TCB {}", endorsed_tcb_hex, @@ -460,8 +422,21 @@ namespace ccf::pal // ---- Set return values ---- - report_data = SnpAttestationReportData(quote.report_data); - measurement = SnpAttestationMeasurement(quote.measurement); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_report_data(attestation.get(), &data, &size); + report_data = SnpAttestationReportData({data, size}); + tav_snp_attestation_report_measurement(attestation.get(), &data, &size); + measurement = SnpAttestationMeasurement({data, size}); + return attestation; + } + + void verify_snp_attestation_report( + const QuoteInfo& quote_info, + PlatformAttestationMeasurement& measurement, + PlatformAttestationReportData& report_data) + { + verify_snp_attestation_report_and_get(quote_info, measurement, report_data); } void verify_quote( diff --git a/src/pal/quote_generation.h b/src/pal/quote_generation.h index 72ec00b8ddea..4c35101ad437 100644 --- a/src/pal/quote_generation.h +++ b/src/pal/quote_generation.h @@ -89,25 +89,27 @@ namespace ccf::pal { QuoteInfo node_quote_info = {}; node_quote_info.format = QuoteFormat::amd_sev_snp_v1; - auto attestation = snp::get_attestation(report_data); + node_quote_info.quote = snp::get_attestation(report_data)->get_raw(); + auto report = + snp::parse_attestation_report_unverified(node_quote_info.quote); - if (attestation->get().version < pal::snp::minimum_attestation_version) + if ( + tav_snp_attestation_report_version(report.get()) < + pal::snp::minimum_attestation_version) { throw std::logic_error(fmt::format( "SEV-SNP: attestation version {} is less than the minimum supported " "version {}", - attestation->get().version, + tav_snp_attestation_report_version(report.get()), pal::snp::minimum_attestation_version)); } - node_quote_info.quote = attestation->get_raw(); - if (endorsement_cb != nullptr) { endorsement_cb( node_quote_info, snp::make_endorsement_endpoint_configuration( - attestation->get(), endorsements_servers)); + report, endorsements_servers)); } } diff --git a/src/pal/test/snp_attestation_validation.cpp b/src/pal/test/snp_attestation_validation.cpp index afc8d026418e..6cd2aa48eee2 100644 --- a/src/pal/test/snp_attestation_validation.cpp +++ b/src/pal/test/snp_attestation_validation.cpp @@ -6,12 +6,14 @@ #include "ccf/ds/hex.h" #include "ccf/ds/logger.h" #include "ccf/ds/quote_info.h" +#include "ccf/node/quote.h" #include "ccf/pal/attestation.h" #include "ccf/pal/attestation_sev_snp.h" #include "ccf/pal/attestation_sev_snp_endorsements.h" #include "ccf/pal/measurement.h" #include "ccf/pal/report_data.h" #include "ccf/pal/sev_snp_cpuid.h" +#include "ccf/pal/snp_ioctl.h" #include "crypto/openssl/hash.h" #include "pal/test/attestation.h" #include "pal/test/attestation_sev_snp_endorsements.h" @@ -20,6 +22,7 @@ #include #include #include +#include #define DOCTEST_CONFIG_IMPLEMENT #include @@ -128,6 +131,270 @@ namespace } } +TEST_CASE("CCF policy is separate from generic TAV verification") +{ + using namespace ccf::pal; + const auto certs = milan_endorsement_certs(); + REQUIRE(certs.size() == 3); + TavSnpAttestationReport* raw_report = nullptr; + const std::unique_ptr error( + tav_verify_snp_attestation( + snp::testing::milan_attestation.data(), + snp::testing::milan_attestation.size(), + certs[2].data(), + certs[2].size(), + certs[1].data(), + certs[1].size(), + certs[0].data(), + certs[0].size(), + &raw_report), + tav_error_free); + const snp::AttestationReport report(raw_report); + REQUIRE(error == nullptr); + REQUIRE(report != nullptr); + CHECK( + tav_snp_attestation_report_version(report.get()) == + snp::minimum_attestation_version); + + PlatformAttestationMeasurement measurement; + PlatformAttestationReportData report_data; + const std::vector endorsements( + snp::testing::milan_endorsements.begin(), + snp::testing::milan_endorsements.end()); + const ccf::QuoteInfo quote_info = { + .format = ccf::QuoteFormat::amd_sev_snp_v1, + .quote = snp::testing::milan_attestation, + .endorsements = endorsements, + .uvm_endorsements = std::nullopt, + .endorsed_tcb = "0000000000000000"}; + CHECK_THROWS_WITH_AS( + verify_snp_attestation_report_and_get(quote_info, measurement, report_data), + doctest::Contains("does not match reported TCB"), + std::logic_error); +} + +TEST_CASE("unverified SNP report rejects invalid sizes") +{ + for (const size_t size : {0U, 100U, 1183U, 1185U}) + { + const auto expected_error = size == 0 ? + "SEV-SNP: TAV unverified report parsing failed (1): attestation report " + "is empty" : + fmt::format( + "SEV-SNP: TAV unverified report parsing failed (1): Invalid " + "attestation report: expected 1184 bytes, got {}", + size); + CHECK_THROWS_WITH_AS( + static_cast(ccf::pal::snp::parse_attestation_report_unverified( + std::vector(size))), + expected_error.c_str(), + std::logic_error); + } +} + +TEST_CASE("SNP chip ID access rejects empty handles") +{ + ccf::pal::snp::AttestationReport report; + CHECK_THROWS_WITH_AS( + ccf::pal::snp::get_chip_id_for_vcek(report), + "Cannot access an empty SNP attestation report", + std::logic_error); +} + +TEST_CASE("SNP endorsement configuration rejects empty owners") +{ + using namespace ccf::pal::snp; + AttestationReport report; + + SUBCASE("default constructed") {} + + SUBCASE("moved from") + { + report = parse_attestation_report_unverified(testing::milan_attestation); + auto owner = std::move(report); + REQUIRE(owner != nullptr); + CHECK_NOTHROW(make_endorsement_endpoint_configuration(owner)); + } + + REQUIRE(report == nullptr); + CHECK_THROWS_WITH_AS( + make_endorsement_endpoint_configuration(report), + "Cannot access an empty SNP attestation report", + std::logic_error); +} + +TEST_CASE("VCEK chip ID uses the product-specific prefix") +{ + using namespace ccf::pal::snp; + struct TestCase + { + const std::vector& report; + size_t chip_id_size; + }; + for (const auto& [raw_report, expected_size] : + {TestCase{testing::milan_attestation, 64}, + TestCase{testing::genoa_attestation, 64}, + TestCase{testing::turin_attestation, 8}}) + { + auto report = parse_attestation_report_unverified(raw_report); + const auto vcek_chip_id = get_chip_id_for_vcek(report); + REQUIRE(vcek_chip_id.size() == expected_size); + CHECK(std::equal( + vcek_chip_id.begin(), vcek_chip_id.end(), raw_report.begin() + 0x1A0)); + } +} + +TEST_CASE("TCB values can be constructed from borrowed bytes") +{ + using ccf::pal::snp::TcbVersionRaw; + std::array bytes = {4, 0, 0, 0, 0, 0, 24, 219}; + const auto tcb = TcbVersionRaw(std::span(bytes)); + CHECK(tcb.to_hex() == "db18000000000004"); + CHECK(tcb == TcbVersionRaw(std::vector(bytes.begin(), bytes.end()))); + bytes.fill(0); + CHECK(tcb.to_hex() == "db18000000000004"); + for (const size_t size : {0, 7, 9}) + { + const std::vector invalid_bytes(size); + const auto expected_error = + fmt::format("Invalid TCB version raw data size: {}", size); + CHECK_THROWS_WITH_AS( + TcbVersionRaw{invalid_bytes}, expected_error.c_str(), std::logic_error); + CHECK_THROWS_WITH_AS( + TcbVersionRaw(std::span(invalid_bytes)), + expected_error.c_str(), + std::logic_error); + } +} + +TEST_CASE("SNP verification preserves invalid size error") +{ + ccf::pal::PlatformAttestationMeasurement measurement; + ccf::pal::PlatformAttestationReportData report_data; + const ccf::QuoteInfo quote_info = { + .format = ccf::QuoteFormat::amd_sev_snp_v1, + .quote = std::vector(100), + .endorsements = {}, + .uvm_endorsements = std::nullopt}; + CHECK_THROWS_WITH_AS( + ccf::pal::verify_snp_attestation_report_and_get( + quote_info, measurement, report_data), + doctest::Contains( + "Input SEV-SNP attestation report is not of expected size 1184: 100"), + std::logic_error); +} + +TEST_CASE("SNP verification rejects other quote formats before parsing") +{ + for (const auto format : + {ccf::QuoteFormat::insecure_virtual, ccf::QuoteFormat::oe_sgx_v1}) + { + const ccf::QuoteInfo quote_info = { + .format = format, + .quote = {}, + .endorsements = {}, + .uvm_endorsements = std::nullopt}; + ccf::pal::PlatformAttestationMeasurement measurement; + ccf::pal::PlatformAttestationReportData report_data; + const auto expected_error = fmt::format( + "Unexpected attestation report to verify for SEV-SNP: {}", format); + CHECK_THROWS_WITH_AS( + ccf::pal::verify_snp_attestation_report_and_get( + quote_info, measurement, report_data), + expected_error.c_str(), + std::logic_error); + CHECK_THROWS_WITH_AS( + ccf::pal::verify_snp_attestation_report( + quote_info, measurement, report_data), + expected_error.c_str(), + std::logic_error); + } +} + +TEST_CASE("SNP request rejects oversized report data before ioctl") +{ + ccf::pal::PlatformAttestationReportData report_data; + report_data.data.resize(ccf::pal::snp_attestation_report_data_size + 1); + CHECK_THROWS_WITH_AS( + ccf::pal::snp::ioctl6::Attestation{report_data}, + "User-defined report data is larger than available space", + std::logic_error); +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +TEST_CASE("legacy SNP report layout matches the AMD specification") +{ + using ccf::pal::snp::Attestation; + + static_assert( + std::is_same_v< + decltype(std::declval().get()), + const Attestation&>); + static_assert( + std::is_same_v< + decltype(std::declval().get_raw()), + std::vector>); + static_assert( + std::is_same_v< + decltype(std::declval().get_raw()), + std::vector>); + static_assert(std::is_same_v< + decltype(ccf::AttestationProvider::get_snp_attestation( + std::declval())), + std::optional>); + static_assert(std::is_same_v< + decltype(ccf::pal::snp::ioctl6::AttestationResp::report), + Attestation>); + + CHECK(ccf::pal::snp::amd_root_signing_keys.size() == 3); + CHECK( + ccf::pal::snp::amd_root_signing_keys.at(ccf::pal::snp::ProductName::Milan) + .public_key == ccf::pal::snp::amd_milan_root_signing_public_key); + + Attestation report = {}; + CHECK(sizeof(report) == ccf::pal::snp::attestation_report_size); + CHECK(alignof(Attestation) == 1); + CHECK(offsetof(Attestation, version) == 0x000); + CHECK(offsetof(Attestation, policy) == 0x008); + CHECK(offsetof(Attestation, report_data) == 0x050); + CHECK(offsetof(Attestation, measurement) == 0x090); + CHECK(offsetof(Attestation, reported_tcb) == 0x180); + CHECK(offsetof(Attestation, chip_id) == 0x1A0); + CHECK(offsetof(Attestation, current_build) == 0x1E8); + CHECK(offsetof(Attestation, current_minor) == 0x1E9); + CHECK(offsetof(Attestation, current_major) == 0x1EA); + CHECK(offsetof(Attestation, signature) == 0x2A0); + + ccf::pal::snp::ioctl6::detail::AttestationResponse response; + response.report_size = ccf::pal::snp::attestation_report_size; + std::copy( + ccf::pal::snp::testing::milan_attestation.begin(), + ccf::pal::snp::testing::milan_attestation.end(), + response.report_bytes.begin()); + ccf::pal::snp::ioctl6::PaddedAttestationResp legacy_response; + static_assert(std::is_trivially_copyable_v); + static_assert(sizeof(legacy_response) == sizeof(response)); + std::memcpy(&legacy_response, &response, sizeof(legacy_response)); + CHECK(legacy_response.report_size == response.report_size); + REQUIRE(response.report_bytes[0x1E8] != response.report_bytes[0x1E9]); + CHECK(legacy_response.report.current_build == response.report_bytes[0x1E8]); + CHECK(legacy_response.report.current_minor == response.report_bytes[0x1E9]); + CHECK( + std::memcmp( + &legacy_response.report, + response.report_bytes.data(), + response.report_bytes.size()) == 0); + + report.version = ccf::pal::snp::minimum_attestation_version; + report.cpuid_fam_id = 0x19; + report.cpuid_mod_id = 0x01; + const auto config = + ccf::pal::snp::make_endorsement_endpoint_configuration(report); + CHECK(config.servers.size() == 1); +} +#pragma clang diagnostic pop + TEST_CASE("milan validation") { using namespace ccf; @@ -144,8 +411,15 @@ TEST_CASE("milan validation") pal::PlatformAttestationMeasurement measurement; pal::PlatformAttestationReportData report_data; + const auto report = pal::verify_snp_attestation_report_and_get( + milan_quote_info, measurement, report_data); + REQUIRE(report != nullptr); + const auto verified_measurement = measurement.data; + const auto verified_report_data = report_data.data; pal::verify_snp_attestation_report( milan_quote_info, measurement, report_data); + CHECK(measurement.data == verified_measurement); + CHECK(report_data.data == verified_report_data); } TEST_CASE("genoa validation") @@ -188,6 +462,31 @@ TEST_CASE("turin validation") turin_quote_info, measurement, report_data); } +TEST_CASE("Invalid attestation signature fails TAV verification") +{ + using namespace ccf; + + auto invalid_attestation = pal::snp::testing::milan_attestation; + static constexpr size_t signature_offset = 0x2a0; + invalid_attestation[signature_offset] ^= 1; + auto quote_info = QuoteInfo{ + .format = QuoteFormat::amd_sev_snp_v1, + .quote = std::move(invalid_attestation), + .endorsements = std::vector( + pal::snp::testing::milan_endorsements.begin(), + pal::snp::testing::milan_endorsements.end()), + .uvm_endorsements = std::nullopt, + }; + + pal::PlatformAttestationMeasurement measurement; + pal::PlatformAttestationReportData report_data; + + CHECK_THROWS_WITH_AS( + pal::verify_snp_attestation_report(quote_info, measurement, report_data), + doctest::Contains("SEV-SNP: TAV verification failed (104):"), + std::logic_error); +} + TEST_CASE("Mismatched attestation and endorsements fail") { using namespace ccf; @@ -207,9 +506,7 @@ TEST_CASE("Mismatched attestation and endorsements fail") CHECK_THROWS_WITH_AS( pal::verify_snp_attestation_report( mismatched_quote, measurement, report_data), - doctest::Contains( - "SEV-SNP: The root of trust public key for this attestation " - "was not the expected one"), + doctest::Contains("SEV-SNP: TAV verification failed (102):"), std::logic_error); } @@ -223,9 +520,7 @@ TEST_CASE("ARK with unexpected issuer fails") CHECK_THROWS_WITH_AS( ccf::pal::verify_snp_attestation_report( quote_info, measurement, report_data), - doctest::Contains( - "SEV-SNP: The root of trust issuer for this attestation was not " - "the expected one"), + doctest::Contains("SEV-SNP: TAV verification failed (102):"), std::logic_error); } @@ -269,11 +564,15 @@ TEST_CASE("Parsing of Tcb versions from strings") TEST_CASE("Parsing tcb versions from attestaion") { - auto milan_attestation = *reinterpret_cast( - ccf::pal::snp::testing::milan_attestation.data()); - auto milan_tcb = - milan_attestation.reported_tcb.to_policy(ccf::pal::snp::ProductName::Milan) - .to_milan_genoa(); + auto milan_attestation = ccf::pal::snp::parse_attestation_report_unverified( + ccf::pal::snp::testing::milan_attestation); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_reported_tcb( + milan_attestation.get(), &data, &size); + auto milan_tcb = ccf::pal::snp::TcbVersionRaw({data, size}) + .to_policy(ccf::pal::snp::ProductName::Milan) + .to_milan_genoa(); CHECK_EQ(milan_tcb.microcode, 0xdb); CHECK_EQ(milan_tcb.snp, 0x18); CHECK_EQ(milan_tcb.tee, 0x00); @@ -488,7 +787,7 @@ TEST_CASE("Quote endorsements url generation") for (auto [attestation, servers, expected_url] : test_cases) { auto quote = - *reinterpret_cast(attestation.data()); + ccf::pal::snp::parse_attestation_report_unverified(attestation); auto config = ccf::pal::snp::make_endorsement_endpoint_configuration(quote, servers); @@ -496,15 +795,52 @@ TEST_CASE("Quote endorsements url generation") } } +TEST_CASE("Quote endorsement TCB formatting preserves leading zeroes") +{ + using namespace ccf::pal::snp; + + for (const auto& expected_tcb : + {"0000000000000000", "0001000000000004", "0b18000000000004"}) + { + auto report = testing::milan_attestation; + const auto tcb_bytes = ccf::ds::from_hex(expected_tcb); + std::reverse_copy( + tcb_bytes.begin(), tcb_bytes.end(), report.begin() + 0x180); + auto quote = parse_attestation_report_unverified(report); + + const auto default_config = make_endorsement_endpoint_configuration(quote); + REQUIRE_EQ(default_config.servers.size(), 1); + REQUIRE_EQ(default_config.servers.front().size(), 1); + CHECK(default_config.servers.front().front().uri.ends_with( + std::string("/") + expected_tcb)); + + const auto config = make_endorsement_endpoint_configuration( + quote, + {{EndorsementsEndpointType::Azure}, {EndorsementsEndpointType::THIM}}); + REQUIRE_EQ(config.servers.size(), 2); + REQUIRE_EQ(config.servers.front().size(), 1); + REQUIRE_EQ(config.servers.back().size(), 1); + CHECK(config.servers.front().front().uri.ends_with( + std::string("/") + expected_tcb)); + CHECK_EQ( + config.servers.back().front().params.at("tcbVersion"), expected_tcb); + } +} + TEST_CASE("Quote endorsements generation for v2 attestation version fails") { auto v2_format_milan_attestation = - *reinterpret_cast( - ccf::pal::snp::testing::v2_format_milan_attestation.data()); + ccf::pal::snp::parse_attestation_report_unverified( + ccf::pal::snp::testing::v2_format_milan_attestation); - CHECK_EQ(v2_format_milan_attestation.version, 2); - CHECK_EQ(v2_format_milan_attestation.cpuid_fam_id, 0x0); - CHECK_EQ(v2_format_milan_attestation.cpuid_mod_id, 0x0); + CHECK_EQ( + tav_snp_attestation_report_version(v2_format_milan_attestation.get()), 2); + CHECK_EQ( + tav_snp_attestation_report_cpuid_fam_id(v2_format_milan_attestation.get()), + 0x0); + CHECK_EQ( + tav_snp_attestation_report_cpuid_mod_id(v2_format_milan_attestation.get()), + 0x0); CHECK_THROWS_WITH( ccf::pal::snp::make_endorsement_endpoint_configuration( @@ -530,8 +866,8 @@ TEST_CASE("Extracting metadata from endorsements") .uvm_endorsements = std::nullopt, }; - auto attestation = *reinterpret_cast( - milan_quote_info.quote.data()); + auto attestation = + pal::snp::parse_attestation_report_unverified(milan_quote_info.quote); auto certificates = ccf::crypto::split_x509_cert_bundle(std::string_view( reinterpret_cast(milan_quote_info.endorsements.data()), @@ -542,14 +878,17 @@ TEST_CASE("Extracting metadata from endorsements") auto endorsed_tcb = pal::get_endorsed_tcb_from_cert( pal::snp::ProductName::Milan, chip_certificate); REQUIRE(endorsed_tcb.has_value()); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_reported_tcb(attestation.get(), &data, &size); CHECK_EQ( nlohmann::json(endorsed_tcb.value()).dump(), - nlohmann::json(attestation.reported_tcb).dump()); + nlohmann::json(pal::snp::TcbVersionRaw({data, size})).dump()); auto endorsed_chip_id = pal::get_endorsed_chip_id_from_cert(chip_certificate); REQUIRE(endorsed_chip_id.has_value()); - auto printable_reported_chip_id = std::span( - attestation.chip_id, attestation.chip_id + sizeof(attestation.chip_id)); + tav_snp_attestation_report_chip_id(attestation.get(), &data, &size); + const auto printable_reported_chip_id = std::span{data, size}; CHECK_EQ( ds::to_hex(endorsed_chip_id.value()), ds::to_hex(printable_reported_chip_id)); diff --git a/src/pal/test/snp_ioctl_test.cpp b/src/pal/test/snp_ioctl_test.cpp index f873733d173e..2cd0d3779f15 100644 --- a/src/pal/test/snp_ioctl_test.cpp +++ b/src/pal/test/snp_ioctl_test.cpp @@ -23,11 +23,14 @@ TEST_CASE("SNP request attestation") snp_report_data.report_data.begin(), snp_report_data.report_data.end(), 0); PlatformAttestationReportData report_data(snp_report_data); - snp::ioctl6::Attestation ioctl_attestation(report_data); - - const snp::Attestation& attestation = ioctl_attestation.get(); - - SnpAttestationReportData attested_report_data(attestation.report_data); + const auto attestation = snp::get_attestation(report_data)->get_raw(); + REQUIRE(attestation.size() == snp::attestation_report_size); + const auto report = snp::parse_attestation_report_unverified(attestation); + + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_report_data(report.get(), &data, &size); + SnpAttestationReportData attested_report_data({data, size}); REQUIRE_EQ(snp_report_data.report_data, attested_report_data.report_data); } diff --git a/src/pal/test/verify_attestation.cpp b/src/pal/test/verify_attestation.cpp index 660380739b1b..ef99ab5f730e 100644 --- a/src/pal/test/verify_attestation.cpp +++ b/src/pal/test/verify_attestation.cpp @@ -18,8 +18,8 @@ void fetch_endorsements( const std::vector& attestation_raw, std::vector& output) { - auto attestation = *reinterpret_cast( - attestation_raw.data()); + auto attestation = + ccf::pal::snp::parse_attestation_report_unverified(attestation_raw); auto endorsement_config = ccf::pal::snp::make_endorsement_endpoint_configuration( @@ -64,15 +64,16 @@ int main(int argc, char** argv) .add_option( "-a,--attestation", attestation_hex, "Attestation in hex format") ->check([](const std::string& attestation_hex) { - auto attest = ccf::ds::from_hex(attestation_hex); - if (attest.size() != sizeof(ccf::pal::snp::Attestation)) + try { - return std::string(fmt::format( - "Attestation size is incorrect {} != {}", - attest.size(), - sizeof(ccf::pal::snp::Attestation))); + static_cast(ccf::pal::snp::parse_attestation_report_unverified( + ccf::ds::from_hex(attestation_hex))); + return std::string(); + } + catch (const std::exception& e) + { + return std::string(e.what()); } - return std::string(); }); ccf::LoggerLevel log_level = ccf::LoggerLevel::INFO; diff --git a/src/pal/test/verify_uvm_attestation_and_endorsements.cpp b/src/pal/test/verify_uvm_attestation_and_endorsements.cpp index f4822e7ce583..aa9c981ead73 100644 --- a/src/pal/test/verify_uvm_attestation_and_endorsements.cpp +++ b/src/pal/test/verify_uvm_attestation_and_endorsements.cpp @@ -241,12 +241,15 @@ int main(int argc, char** argv) "Expected SNP quote format"); LOG_INFO_FMT("Verifying endorsements"); - const auto* attestation_unverified = - reinterpret_cast( - quote_info.quote.data()); + const auto attestation_unverified = + ccf::pal::snp::parse_attestation_report_unverified(quote_info.quote); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_reported_tcb( + attestation_unverified.get(), &data, &size); validate_endorsements( endorsements, - attestation_unverified->reported_tcb, + ccf::pal::snp::TcbVersionRaw({data, size}), quote_info.endorsements); LOG_INFO_FMT("Verifying quote"); diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index f332af2a16b0..7e6e3cae63e1 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -964,35 +964,49 @@ namespace ccf } static void trust_node_snp_tcb_version( - ccf::kv::Tx& tx, pal::snp::Attestation& attestation) + ccf::kv::Tx& tx, const pal::snp::AttestationReport& attestation) { - if (attestation.version < pal::snp::minimum_attestation_version) + if (attestation == nullptr) + { + throw std::logic_error("Cannot access an empty SNP attestation report"); + } + if ( + tav_snp_attestation_report_version(attestation.get()) < + pal::snp::minimum_attestation_version) { throw std::logic_error(fmt::format( "SEV-SNP: attestation version {} is not supported. Minimum " "supported version is {}", - attestation.version, + tav_snp_attestation_report_version(attestation.get()), pal::snp::minimum_attestation_version)); } // As cpuid -> attestation cpuid is surjective, we must use the local // cpuid and validate it against the attestation's cpuid auto cpuid = pal::snp::get_cpuid_untrusted(); if ( - cpuid.get_family_id() != attestation.cpuid_fam_id || - cpuid.get_model_id() != attestation.cpuid_mod_id || - cpuid.stepping != attestation.cpuid_step) + cpuid.get_family_id() != + tav_snp_attestation_report_cpuid_fam_id(attestation.get()) || + cpuid.get_model_id() != + tav_snp_attestation_report_cpuid_mod_id(attestation.get()) || + cpuid.stepping != + tav_snp_attestation_report_cpuid_step(attestation.get())) { throw std::runtime_error(fmt::format( "CPU-sourced cpuid does not match attestation cpuid ({} != {}, {}, " "{})", cpuid.hex_str(), - attestation.cpuid_fam_id, - attestation.cpuid_mod_id, - attestation.cpuid_step)); + tav_snp_attestation_report_cpuid_fam_id(attestation.get()), + tav_snp_attestation_report_cpuid_mod_id(attestation.get()), + tav_snp_attestation_report_cpuid_step(attestation.get()))); } auto* h = tx.wo(Tables::SNP_TCB_VERSIONS); auto product = pal::snp::get_sev_snp_product(cpuid); - h->put(cpuid.hex_str(), attestation.reported_tcb.to_policy(product)); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_reported_tcb(attestation.get(), &data, &size); + h->put( + cpuid.hex_str(), + pal::snp::TcbVersionRaw({data, size}).to_policy(product)); } static void init_configuration( diff --git a/tests/npm_tests.py b/tests/npm_tests.py index ff7877d09dc5..9311c7c8df86 100644 --- a/tests/npm_tests.py +++ b/tests/npm_tests.py @@ -746,6 +746,12 @@ def test_npm_app(network, args): assert r.status_code == http.HTTPStatus.OK, r.status_code report_json = r.body.json()["attestation"] print(f"{report_json=}") + raw_report = b64decode(reference_quote["raw"]) + expected_current_build = raw_report[0x1E8] + expected_current_minor = raw_report[0x1E9] + assert expected_current_build != expected_current_minor + assert report_json["current_build"] == expected_current_build + assert report_json["current_minor"] == expected_current_minor assert report_json[ "report_data" ] == "7a6a68c0a2b85b8aae00ca04f644831680222f44167e5558a9e072b70c60e958" + (