diff --git a/.daily_canary b/.daily_canary index 9ad3cd2975b9..fd07e7d929a7 100644 --- a/.daily_canary +++ b/.daily_canary @@ -1,4 +1,4 @@ ___ ___ - (~ ~) (o o) | Y & + + (- -) (o o) | Y & + ( V ) z O z O +---'---' /--x-m- /--m-m---xXx--/--yy------ diff --git a/.snpcc_canary b/.snpcc_canary index 5c43332e0a81..d684a282ce6c 100644 --- a/.snpcc_canary +++ b/.snpcc_canary @@ -1,4 +1,4 @@ ___ ___ ___ - (O o) Y (9 3) (* *) Y + (O =) Y (9 3) (* *) Y O / O O / /-xXx--//-----x=x--/-xXx--/---x---->xxxxxx diff --git a/CHANGELOG.md b/CHANGELOG.md index a0d141249114..fbaf066f30d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Additional logging of historical query flow in `UNSAFE` builds. - Historical query system will re-request entries if the host fails to provide them within a fixed time. +- Renamed `ccf::CodeDigest` to `ccf:pal::PlatformAttestationMeasurement` and `get_code_id()` to `get_measurement()` (#5063). ### Dependencies diff --git a/doc/schemas/gov_openapi.json b/doc/schemas/gov_openapi.json index b559db38dfbb..149b14fdf288 100644 --- a/doc/schemas/gov_openapi.json +++ b/doc/schemas/gov_openapi.json @@ -63,29 +63,6 @@ }, "type": "object" }, - "CodeDigest": { - "format": "hex", - "pattern": "^[a-f0-9]+$", - "type": "string" - }, - "CodeDigest_to_CodeStatus": { - "items": { - "items": { - "oneOf": [ - { - "$ref": "#/components/schemas/CodeDigest" - }, - { - "$ref": "#/components/schemas/CodeStatus" - } - ] - }, - "maxItems": 2, - "minItems": 2, - "type": "array" - }, - "type": "array" - }, "CodeStatus": { "enum": [ "AllowedToJoin" @@ -951,6 +928,29 @@ ], "type": "string" }, + "SgxAttestationMeasurement": { + "format": "hex", + "pattern": "^[a-f0-9]64$", + "type": "string" + }, + "SgxAttestationMeasurement_to_CodeStatus": { + "items": { + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/SgxAttestationMeasurement" + }, + { + "$ref": "#/components/schemas/CodeStatus" + } + ] + }, + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "type": "array" + }, "Sha256Digest": { "format": "hex", "pattern": "^[a-f0-9]{32}$", @@ -1001,6 +1001,29 @@ ], "type": "object" }, + "SnpAttestationMeasurement": { + "format": "hex", + "pattern": "^[a-f0-9]96$", + "type": "string" + }, + "SnpAttestationMeasurement_to_CodeStatus": { + "items": { + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/SnpAttestationMeasurement" + }, + { + "$ref": "#/components/schemas/CodeStatus" + } + ] + }, + "maxItems": 2, + "minItems": 2, + "type": "array" + }, + "type": "array" + }, "StateDigest": { "properties": { "state_digest": { @@ -1182,7 +1205,7 @@ "info": { "description": "This API is used to submit and query proposals which affect CCF's public governance tables.", "title": "CCF Governance API", - "version": "2.22.0" + "version": "2.23.0" }, "openapi": "3.0.0", "paths": { @@ -1725,7 +1748,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CodeDigest_to_CodeStatus" + "$ref": "#/components/schemas/SgxAttestationMeasurement_to_CodeStatus" } } }, @@ -1813,7 +1836,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/CodeDigest_to_CodeStatus" + "$ref": "#/components/schemas/SnpAttestationMeasurement_to_CodeStatus" } } }, diff --git a/include/ccf/node/quote.h b/include/ccf/node/quote.h index b0fb93d288d9..99e40684bb6e 100644 --- a/include/ccf/node/quote.h +++ b/include/ccf/node/quote.h @@ -4,7 +4,7 @@ #include "ccf/ccf_deprecated.h" #include "ccf/ds/quote_info.h" -#include "ccf/service/code_digest.h" +#include "ccf/pal/measurement.h" #include "ccf/service/tables/host_data.h" #include "ccf/tx.h" @@ -30,7 +30,8 @@ namespace ccf /// In SGX case, extracts the MRENCLAVE from an OE quote. This fails on /// quotes with expired collateral, so it is recommended to cache code IDs /// once they have been successfully extracted. - static std::optional get_code_id(const QuoteInfo& quote_info); + static std::optional get_measurement( + const QuoteInfo& quote_info); static std::optional get_host_data(const QuoteInfo& quote_info); @@ -38,7 +39,7 @@ namespace ccf kv::ReadOnlyTx& tx, const QuoteInfo& quote_info, const std::vector& expected_node_public_key_der, - CodeDigest& code_digest); + pal::PlatformAttestationMeasurement& measurement); }; using EnclaveAttestationProvider CCF_DEPRECATED("Will be removed in 4.x") = diff --git a/include/ccf/pal/attestation.h b/include/ccf/pal/attestation.h index c640c3319fd6..4f809594336e 100644 --- a/include/ccf/pal/attestation.h +++ b/include/ccf/pal/attestation.h @@ -9,6 +9,7 @@ #include "ccf/ds/logger.h" #include "ccf/ds/quote_info.h" #include "ccf/pal/attestation_sev_snp.h" +#include "ccf/pal/measurement.h" #include #include @@ -33,8 +34,8 @@ namespace ccf::pal // SGX, this does not require external dependencies (Open Enclave for SGX). static void verify_snp_attestation_report( const QuoteInfo& quote_info, - attestation_measurement& unique_id, - attestation_report_data& report_data) + PlatformAttestationMeasurement& measurement, + PlatformAttestationReportData& report_data) { if (quote_info.format != QuoteFormat::amd_sev_snp_v1) { @@ -75,14 +76,8 @@ namespace ccf::pal fmt::format("SEV-SNP: Mask chip key must not be set")); } - std::copy( - std::begin(quote.report_data), - std::end(quote.report_data), - report_data.begin()); - std::copy( - std::begin(quote.measurement), - std::end(quote.measurement), - unique_id.begin()); + report_data = SnpAttestationReportData(quote.report_data); + measurement = SnpAttestationMeasurement(quote.measurement); auto certificates = crypto::split_x509_cert_bundle(std::string_view( reinterpret_cast(quote_info.endorsements.data()), @@ -151,7 +146,7 @@ namespace ccf::pal } // We should check this (although not security critical) but the guest - // policy ABI is currently set to 0.31, although we are targetting 1.54 + // policy ABI is currently set to 0.31, although we are targeting 1.54 // if (quote.policy.abi_major < snp::attestation_policy_abi_major) // { // throw std::logic_error(fmt::format( @@ -202,7 +197,7 @@ namespace ccf::pal #if defined(PLATFORM_VIRTUAL) static void generate_quote( - attestation_report_data& report_data, + PlatformAttestationReportData& report_data, RetrieveEndorsementCallback endorsement_cb, const snp::EndorsementsServers& endorsements_servers = {}) { @@ -216,7 +211,7 @@ namespace ccf::pal #elif defined(PLATFORM_SNP) static void generate_quote( - attestation_report_data& report_data, + PlatformAttestationReportData& report_data, RetrieveEndorsementCallback endorsement_cb, const snp::EndorsementsServers& endorsements_servers = {}) { @@ -234,7 +229,9 @@ namespace ccf::pal // Arbitrary report data memcpy( - req.report_data, report_data.data(), snp_attestation_report_data_size); + req.report_data, + report_data.data.data(), + snp_attestation_report_data_size); // Documented at // https://www.kernel.org/doc/html/latest/virt/coco/sev-guest.html @@ -274,8 +271,8 @@ namespace ccf::pal static void verify_quote( const QuoteInfo& quote_info, - attestation_measurement& unique_id, - attestation_report_data& report_data) + PlatformAttestationMeasurement& measurement, + PlatformAttestationReportData& report_data) { auto is_sev_snp = access(snp::DEVICE, F_OK) == 0; @@ -286,8 +283,9 @@ namespace ccf::pal throw std::logic_error( "Cannot verify virtual attestation report if node is SEV-SNP"); } - unique_id = {}; - report_data = {}; + // For now, virtual resembles SGX (mostly for historical reasons) + measurement = SgxAttestationMeasurement(); + report_data = SgxAttestationReportData(); } else if (quote_info.format == QuoteFormat::amd_sev_snp_v1) { @@ -297,7 +295,7 @@ namespace ccf::pal "Cannot verify SEV-SNP attestation report if node is virtual"); } - verify_snp_attestation_report(quote_info, unique_id, report_data); + verify_snp_attestation_report(quote_info, measurement, report_data); } else { @@ -317,7 +315,7 @@ namespace ccf::pal #else // SGX static void generate_quote( - attestation_report_data& report_data, + PlatformAttestationReportData& report_data, RetrieveEndorsementCallback endorsement_cb, const snp::EndorsementsServers& endorsements_servers = {}) { @@ -328,12 +326,11 @@ namespace ccf::pal sgx::Endorsements endorsements; sgx::SerialisedClaims serialised_custom_claims; - // Serialise hash of node's public key as a custom claim const size_t custom_claim_length = 1; oe_claim_t custom_claim; custom_claim.name = const_cast(sgx::report_data_claim_name); - custom_claim.value = report_data.data(); - custom_claim.value_size = report_data.size(); + custom_claim.value = report_data.data.data(); + custom_claim.value_size = report_data.data.size(); auto rc = oe_serialize_custom_claims( &custom_claim, @@ -377,8 +374,8 @@ namespace ccf::pal static void verify_quote( const QuoteInfo& quote_info, - attestation_measurement& unique_id, - attestation_report_data& report_data) + PlatformAttestationMeasurement& measurement, + PlatformAttestationReportData& report_data) { if (quote_info.format == QuoteFormat::insecure_virtual) { @@ -387,7 +384,7 @@ namespace ccf::pal } else if (quote_info.format == QuoteFormat::amd_sev_snp_v1) { - verify_snp_attestation_report(quote_info, unique_id, report_data); + verify_snp_attestation_report(quote_info, measurement, report_data); return; } @@ -410,17 +407,23 @@ namespace ccf::pal oe_result_str(rc))); } - bool unique_id_found = false; - bool sgx_report_data_found = false; + std::optional claim_measurement = std::nullopt; + std::optional custom_claim_report_data = + std::nullopt; for (size_t i = 0; i < claims.length; i++) { auto& claim = claims.data[i]; auto claim_name = std::string(claim.name); if (claim_name == OE_CLAIM_UNIQUE_ID) { - std::copy( - claim.value, claim.value + claim.value_size, unique_id.begin()); - unique_id_found = true; + if (claim.value_size != SgxAttestationMeasurement::size()) + { + throw std::logic_error( + fmt::format("SGX measurement claim is not of expected size")); + } + + claim_measurement = + SgxAttestationMeasurement({claim.value, claim.value_size}); } else if (claim_name == OE_CLAIM_CUSTOM_CLAIMS_BUFFER) { @@ -443,37 +446,38 @@ namespace ccf::pal auto& custom_claim = custom_claims.data[j]; if (std::string(custom_claim.name) == sgx::report_data_claim_name) { - if (custom_claim.value_size != report_data.size()) + if (custom_claim.value_size != SgxAttestationReportData::size()) { throw std::logic_error(fmt::format( - "Expected {} of size {}, had size {}", + "Expected claim {} of size {}, had size {}", sgx::report_data_claim_name, - report_data.size(), + SgxAttestationReportData::size(), custom_claim.value_size)); } - std::copy( - custom_claim.value, - custom_claim.value + custom_claim.value_size, - report_data.begin()); - sgx_report_data_found = true; + custom_claim_report_data = SgxAttestationReportData( + {custom_claim.value, custom_claim.value_size}); + break; } } } } - if (!unique_id_found) + if (!claim_measurement.has_value()) { throw std::logic_error( "Could not find measurement in SGX attestation report"); } - if (!sgx_report_data_found) + if (!custom_claim_report_data.has_value()) { throw std::logic_error( "Could not find report data in SGX attestation report"); } + + measurement = claim_measurement.value(); + report_data = custom_claim_report_data.value(); } #endif diff --git a/include/ccf/pal/attestation_sev_snp.h b/include/ccf/pal/attestation_sev_snp.h index 5754244c3f2b..0920567c149e 100644 --- a/include/ccf/pal/attestation_sev_snp.h +++ b/include/ccf/pal/attestation_sev_snp.h @@ -3,32 +3,23 @@ #pragma once #include "ccf/pal/attestation_sev_snp_endorsements.h" +#include "ccf/pal/measurement.h" +#include "ccf/pal/report_data.h" #include #include #include -namespace ccf::pal +namespace ccf::pal::snp { // Based on the SEV-SNP ABI Spec document at // https://www.amd.com/system/files/TechDocs/56860.pdf - static constexpr size_t snp_attestation_report_data_size = 64; - static constexpr size_t snp_attestation_measurement_size = 48; -#if !defined(INSIDE_ENCLAVE) || defined(VIRTUAL_ENCLAVE) - using attestation_report_data = - std::array; - using attestation_measurement = - std::array; -#endif + static constexpr auto NO_SECURITY_POLICY = ""; - namespace snp - { - static constexpr auto NO_SECURITY_POLICY = ""; - - // From https://developer.amd.com/sev/ - constexpr auto amd_milan_root_signing_public_key = - R"(-----BEGIN PUBLIC KEY----- + // From https://developer.amd.com/sev/ + constexpr auto amd_milan_root_signing_public_key = + R"(-----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0Ld52RJOdeiJlqK2JdsV mD7FktuotWwX1fNgW41XY9Xz1HEhSUmhLz9Cu9DHRlvgJSNxbeYYsnJfvyjx1MfU 0V5tkKiU1EesNFta1kTA0szNisdYc9isqk7mXT5+KfGRbfc4V/9zRIcE8jlHN61S @@ -45,239 +36,236 @@ QPHfbkH0CyPfhl1jWhJFZasCAwEAAQ== )"; #pragma pack(push, 1) - // Table 3 - struct TcbVersion - { - uint8_t boot_loader; - uint8_t tee; - uint8_t reserved[4]; - uint8_t snp; - uint8_t microcode; + // Table 3 + struct TcbVersion + { + uint8_t boot_loader; + uint8_t tee; + uint8_t reserved[4]; + uint8_t snp; + uint8_t microcode; - bool operator==(const TcbVersion&) const = default; - }; + bool operator==(const TcbVersion&) const = default; + }; #pragma pack(pop) - static_assert( - sizeof(TcbVersion) == sizeof(uint64_t), - "Can't cast TcbVersion to uint64_t"); + static_assert( + sizeof(TcbVersion) == sizeof(uint64_t), + "Can't cast TcbVersion to uint64_t"); #pragma pack(push, 1) - struct Signature - { - uint8_t r[72]; - uint8_t s[72]; - uint8_t reserved[512 - 144]; - }; + struct Signature + { + uint8_t r[72]; + uint8_t s[72]; + uint8_t reserved[512 - 144]; + }; #pragma pack(pop) - // Table 105 - enum class SignatureAlgorithm : uint32_t - { - invalid = 0, - ecdsa_p384_sha384 = 1 - }; + // Table 105 + enum class SignatureAlgorithm : uint32_t + { + invalid = 0, + ecdsa_p384_sha384 = 1 + }; #pragma pack(push, 1) - // Table 8 - struct GuestPolicy - { - uint8_t abi_minor; - uint8_t abi_major; - uint8_t smt : 1; - uint8_t reserved : 1; - uint8_t migrate_ma : 1; - uint8_t debug : 1; - uint8_t single_socket : 1; - uint64_t reserved2 : 43; - }; + // Table 8 + struct GuestPolicy + { + uint8_t abi_minor; + uint8_t abi_major; + uint8_t smt : 1; + uint8_t reserved : 1; + uint8_t migrate_ma : 1; + uint8_t debug : 1; + uint8_t single_socket : 1; + uint64_t reserved2 : 43; + }; #pragma pack(pop) - static_assert( - sizeof(GuestPolicy) == sizeof(uint64_t), - "Can't cast GuestPolicy to uint64_t"); + static_assert( + sizeof(GuestPolicy) == sizeof(uint64_t), + "Can't cast GuestPolicy to uint64_t"); - static constexpr uint8_t attestation_flags_signing_key_vcek = 0; + static constexpr uint8_t attestation_flags_signing_key_vcek = 0; #pragma pack(push, 1) - struct Flags - { - uint8_t author_key_en : 1; - uint8_t mask_chip_key : 1; - uint8_t signing_key : 3; - uint64_t reserved : 27; - }; + struct Flags + { + uint8_t author_key_en : 1; + uint8_t mask_chip_key : 1; + uint8_t signing_key : 3; + uint64_t reserved : 27; + }; #pragma pack(pop) - static_assert( - sizeof(Flags) == sizeof(uint32_t), "Can't cast Flags to uint32_t"); + static_assert( + sizeof(Flags) == sizeof(uint32_t), "Can't cast Flags to uint32_t"); #pragma pack(push, 1) - // Table 22 - struct PlatformInfo - { - uint8_t smt_en : 1; - uint8_t tsme_en : 1; - uint64_t reserved : 62; - }; + // Table 22 + struct PlatformInfo + { + uint8_t smt_en : 1; + uint8_t tsme_en : 1; + uint64_t reserved : 62; + }; #pragma pack(pop) - static_assert( - sizeof(PlatformInfo) == sizeof(uint64_t), - "Can't cast PlatformInfo to uint64_t"); + static_assert( + sizeof(PlatformInfo) == sizeof(uint64_t), + "Can't cast PlatformInfo to uint64_t"); #pragma pack(push, 1) - // Table 21 + // Table 21 - static constexpr uint32_t attestation_version = 2; - static constexpr uint32_t attestation_policy_abi_major = 1; + static constexpr uint32_t attestation_version = 2; + static constexpr uint32_t attestation_policy_abi_major = 1; - struct Attestation - { - uint32_t version; /* 0x000 */ - uint32_t guest_svn; /* 0x004 */ - struct GuestPolicy policy; /* 0x008 */ - uint8_t family_id[16]; /* 0x010 */ - uint8_t image_id[16]; /* 0x020 */ - uint32_t vmpl; /* 0x030 */ - SignatureAlgorithm signature_algo; /* 0x034 */ - struct TcbVersion platform_version; /* 0x038 */ - PlatformInfo platform_info; /* 0x040 */ - Flags flags; /* 0x048 */ - uint32_t reserved0; /* 0x04C */ - uint8_t report_data[snp_attestation_report_data_size]; /* 0x050 */ - uint8_t measurement[snp_attestation_measurement_size]; /* 0x090 */ - uint8_t host_data[32]; /* 0x0C0 */ - uint8_t id_key_digest[48]; /* 0x0E0 */ - uint8_t author_key_digest[48]; /* 0x110 */ - uint8_t report_id[32]; /* 0x140 */ - uint8_t report_id_ma[32]; /* 0x160 */ - struct TcbVersion reported_tcb; /* 0x180 */ - uint8_t reserved1[24]; /* 0x188 */ - uint8_t chip_id[64]; /* 0x1A0 */ - struct TcbVersion committed_tcb; /* 0x1E0 */ - uint8_t current_minor; /* 0x1E8 */ - uint8_t current_build; /* 0x1E9 */ - uint8_t current_major; /* 0x1EA */ - uint8_t reserved2; /* 0x1EB */ - uint8_t committed_build; /* 0x1EC */ - uint8_t committed_minor; /* 0x1ED */ - uint8_t committed_major; /* 0x1EE */ - uint8_t reserved3; /* 0x1EF */ - struct TcbVersion launch_tcb; /* 0x1F0 */ - uint8_t reserved4[168]; /* 0x1F8 */ - struct Signature signature; /* 0x2A0 */ - }; + struct Attestation + { + uint32_t version; /* 0x000 */ + uint32_t guest_svn; /* 0x004 */ + struct GuestPolicy policy; /* 0x008 */ + uint8_t family_id[16]; /* 0x010 */ + uint8_t image_id[16]; /* 0x020 */ + uint32_t vmpl; /* 0x030 */ + SignatureAlgorithm signature_algo; /* 0x034 */ + struct TcbVersion platform_version; /* 0x038 */ + PlatformInfo platform_info; /* 0x040 */ + Flags flags; /* 0x048 */ + uint32_t reserved0; /* 0x04C */ + uint8_t report_data[snp_attestation_report_data_size]; /* 0x050 */ + uint8_t measurement[snp_attestation_measurement_size]; /* 0x090 */ + uint8_t host_data[32]; /* 0x0C0 */ + uint8_t id_key_digest[48]; /* 0x0E0 */ + uint8_t author_key_digest[48]; /* 0x110 */ + uint8_t report_id[32]; /* 0x140 */ + uint8_t report_id_ma[32]; /* 0x160 */ + struct TcbVersion reported_tcb; /* 0x180 */ + uint8_t reserved1[24]; /* 0x188 */ + uint8_t chip_id[64]; /* 0x1A0 */ + struct TcbVersion committed_tcb; /* 0x1E0 */ + uint8_t current_minor; /* 0x1E8 */ + uint8_t current_build; /* 0x1E9 */ + uint8_t current_major; /* 0x1EA */ + uint8_t reserved2; /* 0x1EB */ + uint8_t committed_build; /* 0x1EC */ + uint8_t committed_minor; /* 0x1ED */ + uint8_t committed_major; /* 0x1EE */ + uint8_t reserved3; /* 0x1EF */ + struct TcbVersion launch_tcb; /* 0x1F0 */ + uint8_t reserved4[168]; /* 0x1F8 */ + struct Signature signature; /* 0x2A0 */ + }; #pragma pack(pop) - // Table 20 - struct AttestationReq - { - uint8_t report_data[snp_attestation_report_data_size]; - uint32_t vmpl; - uint8_t reserved[28]; - }; + // Table 20 + struct AttestationReq + { + uint8_t report_data[snp_attestation_report_data_size]; + uint32_t vmpl; + uint8_t reserved[28]; + }; - // Table 23 + // Table 23 #pragma pack(push, 1) - struct AttestationResp - { - uint32_t status; - uint32_t report_size; - uint8_t reserved[0x20 - 0x8]; - struct Attestation report; - uint8_t padding[64]; - // padding to the size of SEV_SNP_REPORT_RSP_BUF_SZ (i.e., 1280 bytes) - }; + struct AttestationResp + { + uint32_t status; + uint32_t report_size; + uint8_t reserved[0x20 - 0x8]; + struct Attestation report; + uint8_t padding[64]; + // padding to the size of SEV_SNP_REPORT_RSP_BUF_SZ (i.e., 1280 bytes) + }; #pragma pack(pop) - struct GuestRequest - { - uint8_t req_msg_type; - uint8_t rsp_msg_type; - uint8_t msg_version; - uint16_t request_len; - uint64_t request_uaddr; - uint16_t response_len; - uint64_t response_uaddr; - uint32_t error; /* firmware error code on failure (see psp-sev.h) */ - }; + struct GuestRequest + { + uint8_t req_msg_type; + uint8_t rsp_msg_type; + uint8_t msg_version; + uint16_t request_len; + uint64_t request_uaddr; + uint16_t response_len; + uint64_t response_uaddr; + uint32_t error; /* firmware error code on failure (see psp-sev.h) */ + }; - // Table 99 - enum MsgType - { - MSG_TYPE_INVALID = 0, - MSG_CPUID_REQ, - MSG_CPUID_RSP, - MSG_KEY_REQ, - MSG_KEY_RSP, - MSG_REPORT_REQ, - MSG_REPORT_RSP, - MSG_EXPORT_REQ, - MSG_EXPORT_RSP, - MSG_IMPORT_REQ, - MSG_IMPORT_RSP, - MSG_ABSORB_REQ, - MSG_ABSORB_RSP, - MSG_VMRK_REQ, - MSG_VMRK_RSP, - MSG_TYPE_MAX - }; + // Table 99 + enum MsgType + { + MSG_TYPE_INVALID = 0, + MSG_CPUID_REQ, + MSG_CPUID_RSP, + MSG_KEY_REQ, + MSG_KEY_RSP, + MSG_REPORT_REQ, + MSG_REPORT_RSP, + MSG_EXPORT_REQ, + MSG_EXPORT_RSP, + MSG_IMPORT_REQ, + MSG_IMPORT_RSP, + MSG_ABSORB_REQ, + MSG_ABSORB_RSP, + MSG_VMRK_REQ, + MSG_VMRK_RSP, + MSG_TYPE_MAX + }; - // Changes on 5.19+ kernel - constexpr auto DEVICE = "/dev/sev"; + // Changes on 5.19+ kernel + constexpr auto DEVICE = "/dev/sev"; - static EndorsementEndpointsConfiguration - make_endorsement_endpoint_configuration( - const Attestation& quote, - const snp::EndorsementsServers& endorsements_servers = {}) - { - EndorsementEndpointsConfiguration config; + static EndorsementEndpointsConfiguration + make_endorsement_endpoint_configuration( + const Attestation& quote, + const snp::EndorsementsServers& endorsements_servers = {}) + { + EndorsementEndpointsConfiguration config; - auto chip_id_hex = fmt::format("{:02x}", fmt::join(quote.chip_id, "")); - auto reported_tcb = - fmt::format("{:0x}", *(uint64_t*)("e.reported_tcb)); + auto chip_id_hex = fmt::format("{:02x}", fmt::join(quote.chip_id, "")); + auto reported_tcb = fmt::format("{:0x}", *(uint64_t*)("e.reported_tcb)); - if (endorsements_servers.empty()) - { - // Default to Azure server if no servers are specified - config.servers.emplace_back(make_azure_endorsements_server( - default_azure_endorsements_endpoint_host, chip_id_hex, reported_tcb)); - return config; - } + if (endorsements_servers.empty()) + { + // Default to Azure server if no servers are specified + config.servers.emplace_back(make_azure_endorsements_server( + default_azure_endorsements_endpoint_host, chip_id_hex, reported_tcb)); + return config; + } - for (auto const& server : endorsements_servers) + for (auto const& server : endorsements_servers) + { + switch (server.type) { - switch (server.type) + case EndorsementsEndpointType::Azure: { - case EndorsementsEndpointType::Azure: - { - auto url = - server.url.value_or(default_azure_endorsements_endpoint_host); - config.servers.emplace_back( - make_azure_endorsements_server(url, chip_id_hex, reported_tcb)); - break; - } - case EndorsementsEndpointType::AMD: - { - auto boot_loader = - fmt::format("{}", quote.reported_tcb.boot_loader); - auto tee = fmt::format("{}", quote.reported_tcb.tee); - auto snp = fmt::format("{}", quote.reported_tcb.snp); - auto microcode = fmt::format("{}", quote.reported_tcb.microcode); + auto url = + server.url.value_or(default_azure_endorsements_endpoint_host); + config.servers.emplace_back( + make_azure_endorsements_server(url, chip_id_hex, reported_tcb)); + break; + } + case EndorsementsEndpointType::AMD: + { + auto boot_loader = fmt::format("{}", quote.reported_tcb.boot_loader); + auto tee = fmt::format("{}", quote.reported_tcb.tee); + auto snp = fmt::format("{}", quote.reported_tcb.snp); + auto microcode = fmt::format("{}", quote.reported_tcb.microcode); - auto url = - server.url.value_or(default_azure_endorsements_endpoint_host); - config.servers.emplace_back(make_amd_endorsements_server( - url, chip_id_hex, boot_loader, tee, snp, microcode)); - break; - } - default: - { - throw std::logic_error(fmt::format( - "Unsupported endorsements server type: {}", server.type)); - } + auto url = + server.url.value_or(default_azure_endorsements_endpoint_host); + config.servers.emplace_back(make_amd_endorsements_server( + url, chip_id_hex, boot_loader, tee, snp, microcode)); + break; + } + default: + { + throw std::logic_error(fmt::format( + "Unsupported endorsements server type: {}", server.type)); } } - - return config; } + + return config; } #define SEV_GUEST_IOC_TYPE 'S' diff --git a/include/ccf/pal/attestation_sgx.h b/include/ccf/pal/attestation_sgx.h index 8b73da76d142..b71729925ca4 100644 --- a/include/ccf/pal/attestation_sgx.h +++ b/include/ccf/pal/attestation_sgx.h @@ -3,6 +3,7 @@ #pragma once #if defined(INSIDE_ENCLAVE) && !defined(VIRTUAL_ENCLAVE) + # include # include # include @@ -11,14 +12,6 @@ namespace ccf::pal { - static constexpr size_t sgx_attestation_report_data_size = 32; - static constexpr size_t sgx_attestation_measurement_size = 32; - - using attestation_report_data = - std::array; - using attestation_measurement = - std::array; - namespace sgx { // Set of wrappers for safe memory management diff --git a/include/ccf/pal/measurement.h b/include/ccf/pal/measurement.h new file mode 100644 index 000000000000..c6822f919c2a --- /dev/null +++ b/include/ccf/pal/measurement.h @@ -0,0 +1,166 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the Apache 2.0 License. +#pragma once + +#include "ccf/ds/hex.h" +#include "ccf/ds/json.h" +#include "ccf/kv/serialisers/blit_serialiser.h" + +#include +#include +#include + +namespace ccf::pal +{ + template + struct AttestationMeasurement + { + std::array measurement; + + static size_t size() + { + return N; + } + + std::string hex_str() const + { + return ds::to_hex(measurement); + } + + AttestationMeasurement() = default; + AttestationMeasurement(const std::string& hex_str) + { + ds::from_hex(hex_str, measurement); + } + AttestationMeasurement(std::span data) + { + if (data.size() != size()) + { + throw std::logic_error(fmt::format( + "Cannot initialise AttestationMeasurement with data of size {}, " + "expected {}", + data.size(), + size())); + } + + std::copy(data.data(), data.data() + data.size(), measurement.data()); + } + }; + + template + struct is_attestation_measurement : std::false_type + {}; + + template + struct is_attestation_measurement> : std::true_type + {}; + + template + inline void to_json( + nlohmann::json& j, const AttestationMeasurement& measurement) + { + j = measurement.hex_str(); + } + + template + inline void from_json( + const nlohmann::json& j, AttestationMeasurement& measurement) + { + if (j.is_string()) + { + measurement = j.get(); + } + else + { + throw JsonParseError(fmt::format( + "Attestation measurement should be hex-encoded string: {}", j.dump())); + } + } + + template + inline void fill_json_schema( + nlohmann::json& schema, const AttestationMeasurement*) + { + schema["type"] = "string"; + + // According to the spec, "format is an open value, so you can use any + // formats, even not those defined by the OpenAPI Specification" + // https://swagger.io/docs/specification/data-models/data-types/#format + schema["format"] = "hex"; + schema["pattern"] = + fmt::format("^[a-f0-9]{}$", AttestationMeasurement::size() * 2); + } + + // SGX + static constexpr size_t sgx_attestation_measurement_size = 32; + using SgxAttestationMeasurement = + AttestationMeasurement; + + inline std::string schema_name(const SgxAttestationMeasurement*) + { + return "SgxAttestationMeasurement"; + } + + // SNP + static constexpr size_t snp_attestation_measurement_size = 48; + using SnpAttestationMeasurement = + AttestationMeasurement; + + inline std::string schema_name(const SnpAttestationMeasurement*) + { + return "SnpAttestationMeasurement"; + } + + // Generic wrapper for code digests on all TEE platforms + struct PlatformAttestationMeasurement + { + std::vector data; + + PlatformAttestationMeasurement() = default; + PlatformAttestationMeasurement(const PlatformAttestationMeasurement&) = + default; + + template + PlatformAttestationMeasurement( + const AttestationMeasurement& measurement) : + data(measurement.measurement.begin(), measurement.measurement.end()) + {} + + PlatformAttestationMeasurement& operator=( + const PlatformAttestationMeasurement&) = default; + + std::string hex_str() const + { + return ds::to_hex(data); + } + + operator std::span() const + { + return data; + } + }; + DECLARE_JSON_TYPE(PlatformAttestationMeasurement); + DECLARE_JSON_REQUIRED_FIELDS(PlatformAttestationMeasurement, data); +} + +namespace kv::serialisers +{ + template + struct BlitSerialiser> + { + static SerialisedEntry to_serialised( + const ccf::pal::AttestationMeasurement& measurement) + { + auto hex_str = measurement.hex_str(); + return SerialisedEntry(hex_str.begin(), hex_str.end()); + } + + static ccf::pal::AttestationMeasurement from_serialised( + const SerialisedEntry& data) + { + ccf::pal::AttestationMeasurement ret; + ds::from_hex(std::string(data.data(), data.end()), ret.measurement); + return ret; + } + }; +} \ No newline at end of file diff --git a/include/ccf/pal/report_data.h b/include/ccf/pal/report_data.h new file mode 100644 index 000000000000..8b1218e59834 --- /dev/null +++ b/include/ccf/pal/report_data.h @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the Apache 2.0 License. +#pragma once + +#include "ccf/crypto/sha256_hash.h" + +#include +#include +#include + +namespace ccf::pal +{ + template + struct AttestationReportData + { + std::array report_data; + + static size_t size() + { + return N; + } + + AttestationReportData() = default; + AttestationReportData(std::span data) + { + if (data.size() != size()) + { + throw std::logic_error(fmt::format( + "Cannot initialise AttestationReportData with data of size {}, " + "expected {}", + data.size(), + size())); + } + + std::copy(data.data(), data.data() + data.size(), report_data.data()); + } + }; + + // SGX + static constexpr size_t sgx_attestation_report_data_size = 32; + using SgxAttestationReportData = + AttestationReportData; + + // SNP + static constexpr size_t snp_attestation_report_data_size = 64; + using SnpAttestationReportData = + AttestationReportData; + + // Generic wrapper for attestation report data for _all_ platforms. + struct PlatformAttestationReportData + { + std::vector data; + + PlatformAttestationReportData() = default; + + PlatformAttestationReportData(const crypto::Sha256Hash& hash) : + data(hash.h.begin(), hash.h.end()) + {} + + template + PlatformAttestationReportData(const AttestationReportData& report_data) : + data(report_data.report_data.begin(), report_data.report_data.end()) + {} + + crypto::Sha256Hash to_sha256_hash() const + { + std::span s( + data.data(), crypto::Sha256Hash::SIZE); + return crypto::Sha256Hash::from_span(s); + } + }; +} \ No newline at end of file diff --git a/include/ccf/service/code_digest.h b/include/ccf/service/code_digest.h deleted file mode 100644 index a692172d2e97..000000000000 --- a/include/ccf/service/code_digest.h +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the Apache 2.0 License. -#pragma once - -#include "ccf/crypto/sha256_hash.h" -#include "ccf/ds/hex.h" -#include "ccf/ds/json.h" - -#if !defined(INSIDE_ENCLAVE) || defined(VIRTUAL_ENCLAVE) -# include "ccf/pal/attestation_sev_snp.h" -#else -# include "ccf/pal/attestation_sgx.h" -#endif - -namespace ccf -{ - struct CodeDigest - { - pal::attestation_measurement data = {}; - - CodeDigest() = default; - CodeDigest(const CodeDigest&) = default; - - CodeDigest& operator=(const CodeDigest&) = default; - - std::string hex_str() const - { - return ds::to_hex(data); - } - }; - - inline void to_json(nlohmann::json& j, const CodeDigest& code_digest) - { - j = code_digest.hex_str(); - } - - inline void from_json(const nlohmann::json& j, CodeDigest& code_digest) - { - if (j.is_string()) - { - auto value = j.get(); - ds::from_hex(value, code_digest.data); - } - else - { - throw JsonParseError( - fmt::format("Code Digest should be hex-encoded string: {}", j.dump())); - } - } - - inline std::string schema_name(const CodeDigest*) - { - return "CodeDigest"; - } - - inline void fill_json_schema(nlohmann::json& schema, const CodeDigest*) - { - schema["type"] = "string"; - - // According to the spec, "format is an open value, so you can use any - // formats, even not those defined by the OpenAPI Specification" - // https://swagger.io/docs/specification/data-models/data-types/#format - schema["format"] = "hex"; - // NB: We are not specific about the length of the pattern here, because it - // varies by target platform - schema["pattern"] = "^[a-f0-9]+$"; - } - - enum class CodeStatus - { - ALLOWED_TO_JOIN = 0 - }; - DECLARE_JSON_ENUM( - CodeStatus, {{CodeStatus::ALLOWED_TO_JOIN, "AllowedToJoin"}}); -} - -namespace kv::serialisers -{ - template <> - struct BlitSerialiser - { - static SerialisedEntry to_serialised(const ccf::CodeDigest& code_digest) - { - auto hex_str = ds::to_hex(code_digest.data); - return SerialisedEntry(hex_str.begin(), hex_str.end()); - } - - static ccf::CodeDigest from_serialised(const SerialisedEntry& data) - { - ccf::CodeDigest ret; - ds::from_hex(std::string(data.data(), data.end()), ret.data); - return ret; - } - }; -} diff --git a/include/ccf/service/code_status.h b/include/ccf/service/code_status.h new file mode 100644 index 000000000000..3ff84a6e83d3 --- /dev/null +++ b/include/ccf/service/code_status.h @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the Apache 2.0 License. +#pragma once + +#include "ccf/ds/hex.h" +#include "ccf/ds/json.h" +#include "ccf/pal/measurement.h" + +namespace ccf +{ + enum class CodeStatus + { + ALLOWED_TO_JOIN = 0 + }; + DECLARE_JSON_ENUM( + CodeStatus, {{CodeStatus::ALLOWED_TO_JOIN, "AllowedToJoin"}}); +} \ No newline at end of file diff --git a/include/ccf/service/tables/code_id.h b/include/ccf/service/tables/code_id.h index 1217554671e6..03a76ac8c590 100644 --- a/include/ccf/service/tables/code_id.h +++ b/include/ccf/service/tables/code_id.h @@ -2,12 +2,13 @@ // Licensed under the Apache 2.0 License. #pragma once -#include "ccf/service/code_digest.h" +#include "ccf/pal/measurement.h" +#include "ccf/service/code_status.h" #include "ccf/service/map.h" namespace ccf { - using CodeIDs = ServiceMap; + using CodeIDs = ServiceMap; namespace Tables { diff --git a/include/ccf/service/tables/snp_measurements.h b/include/ccf/service/tables/snp_measurements.h index cff2da744e5b..c212839c111b 100644 --- a/include/ccf/service/tables/snp_measurements.h +++ b/include/ccf/service/tables/snp_measurements.h @@ -2,12 +2,14 @@ // Licensed under the Apache 2.0 License. #pragma once -#include "ccf/service/code_digest.h" +#include "ccf/pal/measurement.h" +#include "ccf/service/code_status.h" #include "ccf/service/map.h" namespace ccf { - using SnpMeasurements = ServiceMap; + using SnpMeasurements = + ServiceMap; namespace Tables { diff --git a/src/apps/external_executor/executor_code_id.h b/src/apps/external_executor/executor_code_id.h index db085c1eda66..75148e5b2fe7 100644 --- a/src/apps/external_executor/executor_code_id.h +++ b/src/apps/external_executor/executor_code_id.h @@ -4,7 +4,8 @@ #include "ccf/ds/json.h" #include "ccf/node/quote.h" -#include "ccf/service/code_digest.h" +#include "ccf/pal/attestation_sev_snp.h" +#include "ccf/pal/measurement.h" #include "ccf/service/map.h" #include "ccf/service/tables/code_id.h" #include "endpoints/grpc/status.h" @@ -17,7 +18,7 @@ namespace externalexecutor kv::ReadOnlyTx& tx, const externalexecutor::protobuf::Attestation& quote_info, const std::string& expected_node_public_key_der, - ccf::CodeDigest& code_digest) + ccf::pal::PlatformAttestationMeasurement& measurement) { return ccf::QuoteVerificationResult::Verified; } @@ -92,7 +93,8 @@ namespace externalexecutor DECLARE_JSON_TYPE(ExecutorCodeInfo); DECLARE_JSON_REQUIRED_FIELDS(ExecutorCodeInfo, status, platform); - using ExecutorCodeIDs = ccf::ServiceMap; + using ExecutorCodeIDs = + ccf::ServiceMap; static constexpr auto EXECUTOR_CODE_IDS = "public:ccf.gov.nodes.executor_code_ids"; diff --git a/src/apps/external_executor/external_executor.cpp b/src/apps/external_executor/external_executor.cpp index 75658f54c517..3fd55b9e8b12 100644 --- a/src/apps/external_executor/external_executor.cpp +++ b/src/apps/external_executor/external_executor.cpp @@ -306,8 +306,10 @@ namespace externalexecutor auto executor_code_ids = ctx.tx.template ro(EXECUTOR_CODE_IDS); executor_code_ids->foreach( - [&out](const ccf::CodeDigest& cd, const ExecutorCodeInfo& info) { - auto digest = ds::to_hex(cd.data); + [&out]( + const ccf::pal::SnpAttestationMeasurement& measurement, + const ExecutorCodeInfo& info) { + auto digest = measurement.hex_str(); out.versions.push_back({digest, info.status, info.platform}); return true; }); @@ -329,9 +331,9 @@ namespace externalexecutor -> ccf::grpc::GrpcAdapterResponse< externalexecutor::protobuf::RegistrationResult> { // verify quote - ccf::CodeDigest code_digest; + ccf::pal::PlatformAttestationMeasurement measurement; ccf::QuoteVerificationResult verify_result = verify_executor_quote( - ctx.tx, payload.attestation(), payload.cert(), code_digest); + ctx.tx, payload.attestation(), payload.cert(), measurement); if (verify_result != ccf::QuoteVerificationResult::Verified) { diff --git a/src/endpoints/common_endpoint_registry.cpp b/src/endpoints/common_endpoint_registry.cpp index 73e962959cca..df04e78f46bc 100644 --- a/src/endpoints/common_endpoint_registry.cpp +++ b/src/endpoints/common_endpoint_registry.cpp @@ -251,8 +251,10 @@ namespace ccf auto codes_ids = ctx.tx.template ro(Tables::NODE_CODE_IDS); codes_ids->foreach( - [&out](const ccf::CodeDigest& cd, const ccf::CodeStatus& status) { - auto digest = ds::to_hex(cd.data); + [&out]( + const ccf::pal::SgxAttestationMeasurement& measurement, + const ccf::CodeStatus& status) { + auto digest = measurement.hex_str(); out.versions.push_back({digest, status}); return true; }); @@ -275,8 +277,10 @@ namespace ccf auto measurements = ctx.tx.template ro(Tables::NODE_SNP_MEASUREMENTS); measurements->foreach( - [&out](const ccf::CodeDigest& cd, const ccf::CodeStatus& status) { - auto digest = ds::to_hex(cd.data); + [&out]( + const ccf::pal::SnpAttestationMeasurement& measurement, + const ccf::CodeStatus& status) { + auto digest = measurement.hex_str(); out.versions.push_back({digest, status}); return true; }); diff --git a/src/node/node_state.h b/src/node/node_state.h index 0d668f52b522..5458316a8656 100644 --- a/src/node/node_state.h +++ b/src/node/node_state.h @@ -90,7 +90,7 @@ namespace ccf crypto::Pem self_signed_node_cert; std::optional endorsed_node_cert = std::nullopt; QuoteInfo quote_info; - CodeDigest node_measurement; + pal::PlatformAttestationMeasurement node_measurement; StartupConfig config; std::optional snp_uvm_endorsements = std::nullopt; std::vector startup_snapshot; @@ -241,10 +241,10 @@ namespace ccf kv::ReadOnlyTx& tx, const QuoteInfo& quote_info, const std::vector& expected_node_public_key_der, - CodeDigest& code_digest) override + pal::PlatformAttestationMeasurement& measurement) override { return AttestationProvider::verify_quote_against_store( - tx, quote_info, expected_node_public_key_der, code_digest); + tx, quote_info, expected_node_public_key_der, measurement); } // @@ -286,33 +286,10 @@ namespace ccf // void launch_node() { - auto code_id = AttestationProvider::get_code_id(quote_info); - if (code_id.has_value()) + auto measurement = AttestationProvider::get_measurement(quote_info); + if (measurement.has_value()) { - node_measurement = code_id.value(); - - if (!config.attestation.environment.uvm_endorsements.has_value()) - { - LOG_INFO_FMT( - "UVM endorsements not set, skipping check against attestation " - "measurement"); - } - else - { - try - { - auto uvm_endorsements_raw = crypto::raw_from_b64( - config.attestation.environment.uvm_endorsements.value()); - snp_uvm_endorsements = - verify_uvm_endorsements(uvm_endorsements_raw, node_measurement); - quote_info.uvm_endorsements = uvm_endorsements_raw; - } - catch (const std::exception& e) - { - throw std::logic_error( - fmt::format("Error verifying UVM endorsements: {}", e.what())); - } - } + node_measurement = measurement.value(); } else { @@ -354,6 +331,29 @@ namespace ccf "Successfully verified attested security policy {}", security_policy_digest); } + + if (!config.attestation.environment.uvm_endorsements.has_value()) + { + LOG_INFO_FMT( + "UVM endorsements not set, skipping check against attestation " + "measurement"); + } + else + { + try + { + auto uvm_endorsements_raw = crypto::raw_from_b64( + config.attestation.environment.uvm_endorsements.value()); + snp_uvm_endorsements = + verify_uvm_endorsements(uvm_endorsements_raw, node_measurement); + quote_info.uvm_endorsements = uvm_endorsements_raw; + } + catch (const std::exception& e) + { + throw std::logic_error( + fmt::format("Error verifying UVM endorsements: {}", e.what())); + } + } } switch (start_type) @@ -476,12 +476,8 @@ namespace ccf launch_node(); }; - pal::attestation_report_data report_data = {}; - crypto::Sha256Hash node_pub_key_hash((node_sign_kp->public_key_der())); - std::copy( - node_pub_key_hash.h.begin(), - node_pub_key_hash.h.end(), - report_data.begin()); + pal::PlatformAttestationReportData report_data = + crypto::Sha256Hash((node_sign_kp->public_key_der())); pal::generate_quote( report_data, @@ -1924,7 +1920,7 @@ namespace ccf create_params.service_cert = network.identity->cert; create_params.quote_info = quote_info; create_params.public_encryption_key = node_encrypt_kp->public_key_pem(); - create_params.code_digest = node_measurement; + create_params.measurement = node_measurement; create_params.snp_uvm_endorsements = snp_uvm_endorsements; create_params.snp_security_policy = config.attestation.environment.security_policy; diff --git a/src/node/quote.cpp b/src/node/quote.cpp index 5077a1f2407e..9396013bebc4 100644 --- a/src/node/quote.cpp +++ b/src/node/quote.cpp @@ -13,7 +13,7 @@ namespace ccf { bool verify_enclave_measurement_against_uvm_endorsements( kv::ReadOnlyTx& tx, - const CodeDigest& quote_measurement, + const pal::PlatformAttestationMeasurement& quote_measurement, const std::vector& uvm_endorsements) { auto uvm_endorsements_data = @@ -47,7 +47,7 @@ namespace ccf QuoteVerificationResult verify_enclave_measurement_against_store( kv::ReadOnlyTx& tx, - const CodeDigest& quote_measurement, + const pal::PlatformAttestationMeasurement& quote_measurement, const QuoteFormat& quote_format, const std::optional>& uvm_endorsements = std::nullopt) { @@ -56,7 +56,7 @@ namespace ccf case QuoteFormat::oe_sgx_v1: { if (!tx.ro(Tables::NODE_CODE_IDS) - ->get(quote_measurement) + ->get(pal::SgxAttestationMeasurement(quote_measurement)) .has_value()) { return QuoteVerificationResult::FailedMeasurementNotFound; @@ -77,10 +77,9 @@ namespace ccf } else { - auto measurement = - tx.ro(Tables::NODE_SNP_MEASUREMENTS) - ->get(quote_measurement); - if (!measurement.has_value()) + if (!tx.ro(Tables::NODE_SNP_MEASUREMENTS) + ->get(pal::SnpAttestationMeasurement(quote_measurement)) + .has_value()) { return QuoteVerificationResult::FailedMeasurementNotFound; } @@ -110,14 +109,14 @@ namespace ccf return QuoteVerificationResult::Verified; } - std::optional AttestationProvider::get_code_id( - const QuoteInfo& quote_info) + std::optional AttestationProvider:: + get_measurement(const QuoteInfo& quote_info) { - CodeDigest measurement = {}; - pal::attestation_report_data r = {}; + pal::PlatformAttestationMeasurement measurement = {}; + pal::PlatformAttestationReportData r = {}; try { - pal::verify_quote(quote_info, measurement.data, r); + pal::verify_quote(quote_info, measurement, r); } catch (const std::exception& e) { @@ -138,11 +137,11 @@ namespace ccf HostData digest{}; HostData::Representation rep{}; - CodeDigest d = {}; - pal::attestation_report_data r = {}; + pal::PlatformAttestationMeasurement d = {}; + pal::PlatformAttestationReportData r = {}; try { - pal::verify_quote(quote_info, d.data, r); + pal::verify_quote(quote_info, d, r); auto quote = *reinterpret_cast( quote_info.quote.data()); std::copy( @@ -186,19 +185,14 @@ namespace ccf kv::ReadOnlyTx& tx, const QuoteInfo& quote_info, const std::vector& expected_node_public_key_der, - CodeDigest& code_digest) + pal::PlatformAttestationMeasurement& measurement) { crypto::Sha256Hash quoted_hash; - pal::attestation_report_data report; + pal::PlatformAttestationReportData report_data; try { - pal::verify_quote(quote_info, code_digest.data, report); - - // Attestation report may be different sizes depending on the platform. - std::copy( - report.begin(), - report.begin() + crypto::Sha256Hash::SIZE, - quoted_hash.h.begin()); + pal::verify_quote(quote_info, measurement, report_data); + quoted_hash = report_data.to_sha256_hash(); } catch (const std::exception& e) { @@ -221,7 +215,7 @@ namespace ccf } auto rc = verify_enclave_measurement_against_store( - tx, code_digest, quote_info.format, quote_info.uvm_endorsements); + tx, measurement, quote_info.format, quote_info.uvm_endorsements); if (rc != QuoteVerificationResult::Verified) { return rc; diff --git a/src/node/rpc/member_frontend.h b/src/node/rpc/member_frontend.h index 9a67cbf07651..db826484184a 100644 --- a/src/node/rpc/member_frontend.h +++ b/src/node/rpc/member_frontend.h @@ -494,8 +494,8 @@ namespace ccf { handle->foreach([&response_body](const auto& k, const auto& v) { if constexpr ( - std::is_same_v || - std::is_same_v) + std::is_same_v || + pal::is_attestation_measurement::value) { response_body[k.hex_str()] = v; } @@ -581,7 +581,7 @@ namespace ccf openapi_info.description = "This API is used to submit and query proposals which affect CCF's " "public governance tables."; - openapi_info.document_version = "2.22.0"; + openapi_info.document_version = "2.23.0"; } static std::optional get_caller_member_id( diff --git a/src/node/rpc/node_call_types.h b/src/node/rpc/node_call_types.h index fd7c36e49a8d..ad9e1950f16d 100644 --- a/src/node/rpc/node_call_types.h +++ b/src/node/rpc/node_call_types.h @@ -60,7 +60,7 @@ namespace ccf crypto::Pem service_cert; QuoteInfo quote_info; crypto::Pem public_encryption_key; - CodeDigest code_digest; + pal::PlatformAttestationMeasurement measurement; std::optional snp_security_policy = std::nullopt; // base64-encoded std::optional snp_uvm_endorsements = std::nullopt; diff --git a/src/node/rpc/node_frontend.h b/src/node/rpc/node_frontend.h index fbe28b24987c..5880d06c358c 100644 --- a/src/node/rpc/node_frontend.h +++ b/src/node/rpc/node_frontend.h @@ -280,10 +280,10 @@ namespace ccf auto pubk_der = crypto::public_key_der_from_cert(node_der); NodeId joining_node_id = compute_node_id_from_pubk_der(pubk_der); - CodeDigest code_digest; + pal::PlatformAttestationMeasurement measurement; QuoteVerificationResult verify_result = this->node_operation.verify_quote( - tx, in.quote_info, pubk_der, code_digest); + tx, in.quote_info, pubk_der, measurement); if (verify_result != QuoteVerificationResult::Verified) { const auto [code, message] = quote_verification_error(verify_result); @@ -321,7 +321,7 @@ namespace ccf in.public_encryption_key, node_status, ledger_secret_seqno, - ds::to_hex(code_digest.data), + measurement.hex_str(), in.certificate_signing_request, client_public_key_pem, in.node_data}; @@ -720,12 +720,12 @@ namespace ccf q.format = node_quote_info.format; q.uvm_endorsements = node_quote_info.uvm_endorsements; - // get_code_id attempts to re-validate the quote to extract mrenclave - // and the Open Enclave is insufficiently flexible to allow quotes - // with expired collateral to be parsed at all. Recent nodes therefore - // cache their code digest on startup, and this code attempts to fetch - // that value when possible and only call the unreliable get_code_id - // otherwise. + // get_measurement attempts to re-validate the quote to extract + // mrenclave and the Open Enclave is insufficiently flexible to allow + // quotes with expired collateral to be parsed at all. Recent nodes + // therefore cache their code digest on startup, and this code + // attempts to fetch that value when possible and only call the + // unreliable get_measurement otherwise. auto nodes = args.tx.ro(network.nodes); auto node_info = nodes->get(context.get_node_id()); if (node_info.has_value() && node_info->code_digest.has_value()) @@ -734,10 +734,11 @@ namespace ccf } else { - auto code_id = AttestationProvider::get_code_id(node_quote_info); - if (code_id.has_value()) + auto measurement = + AttestationProvider::get_measurement(node_quote_info); + if (measurement.has_value()) { - q.mrenclave = ds::to_hex(code_id.value().data); + q.mrenclave = measurement.value().hex_str(); } else { @@ -790,23 +791,23 @@ namespace ccf q.endorsements = node_info.quote_info.endorsements; q.format = node_info.quote_info.format; - // get_code_id attempts to re-validate the quote to extract + // get_measurement attempts to re-validate the quote to extract // mrenclave and the Open Enclave is insufficiently flexible to // allow quotes with expired collateral to be parsed at all. Recent // nodes therefore cache their code digest on startup, and this code // attempts to fetch that value when possible and only call the - // unreliable get_code_id otherwise. + // unreliable get_measurement otherwise. if (node_info.code_digest.has_value()) { q.mrenclave = node_info.code_digest.value(); } else { - auto code_id = - AttestationProvider::get_code_id(node_info.quote_info); - if (code_id.has_value()) + auto measurement = + AttestationProvider::get_measurement(node_info.quote_info); + if (measurement.has_value()) { - q.mrenclave = ds::to_hex(code_id.value().data); + q.mrenclave = measurement.value().hex_str(); } } quotes.emplace_back(q); @@ -1483,6 +1484,7 @@ namespace ccf } const auto in = params.get(); + GenesisGenerator g(this->network, ctx.tx); if (g.is_service_created(in.service_cert)) { @@ -1547,7 +1549,7 @@ namespace ccf in.public_encryption_key, NodeStatus::TRUSTED, std::nullopt, - ds::to_hex(in.code_digest.data), + in.measurement.hex_str(), in.certificate_signing_request, in.public_key, in.node_data}; @@ -1558,7 +1560,7 @@ namespace ccf { // For improved serviceability on SNP, do not record trusted // measurements if UVM endorsements are available - g.trust_node_measurement(in.code_digest, in.quote_info.format); + g.trust_node_measurement(in.measurement, in.quote_info.format); } if (in.quote_info.format == QuoteFormat::amd_sev_snp_v1) { diff --git a/src/node/rpc/node_interface.h b/src/node/rpc/node_interface.h index bbedfd92cf6e..c5a804a8d1c2 100644 --- a/src/node/rpc/node_interface.h +++ b/src/node/rpc/node_interface.h @@ -51,7 +51,7 @@ namespace ccf kv::ReadOnlyTx& tx, const QuoteInfo& quote_info, const std::vector& expected_node_public_key_der, - CodeDigest& code_digest) = 0; + pal::PlatformAttestationMeasurement& measurement) = 0; virtual kv::Version get_startup_snapshot_seqno() = 0; virtual SessionMetrics get_session_metrics() = 0; virtual size_t get_jwt_attempts() = 0; diff --git a/src/node/rpc/node_operation.h b/src/node/rpc/node_operation.h index 82b0a49efd47..748f68b49039 100644 --- a/src/node/rpc/node_operation.h +++ b/src/node/rpc/node_operation.h @@ -74,10 +74,10 @@ namespace ccf kv::ReadOnlyTx& tx, const QuoteInfo& quote_info, const std::vector& expected_node_public_key_der, - CodeDigest& code_digest) override + pal::PlatformAttestationMeasurement& measurement) override { return impl.verify_quote( - tx, quote_info, expected_node_public_key_der, code_digest); + tx, quote_info, expected_node_public_key_der, measurement); } void initiate_private_recovery(kv::Tx& tx) override diff --git a/src/node/rpc/node_operation_interface.h b/src/node/rpc/node_operation_interface.h index 8d6b3eee4825..b08caea70270 100644 --- a/src/node/rpc/node_operation_interface.h +++ b/src/node/rpc/node_operation_interface.h @@ -48,7 +48,7 @@ namespace ccf kv::ReadOnlyTx& tx, const QuoteInfo& quote_info, const std::vector& expected_node_public_key_der, - CodeDigest& code_digest) = 0; + pal::PlatformAttestationMeasurement& measurement) = 0; virtual void initiate_private_recovery(kv::Tx& tx) = 0; diff --git a/src/node/rpc/serialization.h b/src/node/rpc/serialization.h index 2d356f69b84e..16d229bda551 100644 --- a/src/node/rpc/serialization.h +++ b/src/node/rpc/serialization.h @@ -71,7 +71,7 @@ namespace ccf service_cert, quote_info, public_encryption_key, - code_digest, + measurement, node_info_network, create_txid) DECLARE_JSON_OPTIONAL_FIELDS( diff --git a/src/node/rpc/test/node_stub.h b/src/node/rpc/test/node_stub.h index f9c87aa581b8..77f709169cd5 100644 --- a/src/node/rpc/test/node_stub.h +++ b/src/node/rpc/test/node_stub.h @@ -71,7 +71,7 @@ namespace ccf kv::ReadOnlyTx& tx, const QuoteInfo& quote_info, const std::vector& expected_node_public_key_der, - CodeDigest& code_digest) override + pal::PlatformAttestationMeasurement& measurement) override { return QuoteVerificationResult::Verified; } diff --git a/src/node/uvm_endorsements.h b/src/node/uvm_endorsements.h index b123899e7533..32392a33620e 100644 --- a/src/node/uvm_endorsements.h +++ b/src/node/uvm_endorsements.h @@ -236,7 +236,7 @@ namespace ccf static UVMEndorsements verify_uvm_endorsements( const std::vector& uvm_endorsements_raw, - const CodeDigest& uvm_measurement) + const pal::PlatformAttestationMeasurement& uvm_measurement) { auto phdr = cose::decode_protected_header(uvm_endorsements_raw); diff --git a/src/service/genesis_gen.h b/src/service/genesis_gen.h index babe1ccb8934..b302a1d3ae7b 100644 --- a/src/service/genesis_gen.h +++ b/src/service/genesis_gen.h @@ -3,6 +3,7 @@ #pragma once #include "ccf/crypto/verifier.h" +#include "ccf/pal/attestation_sev_snp.h" #include "ccf/service/tables/code_id.h" #include "ccf/service/tables/members.h" #include "ccf/service/tables/nodes.h" @@ -414,22 +415,28 @@ namespace ccf } void trust_node_measurement( - const CodeDigest& node_code_id, const QuoteFormat& platform) + const pal::PlatformAttestationMeasurement& node_measurement, + const QuoteFormat& platform) { switch (platform) { - // For now, record null code id for virtual platform in code id table + // For now, record null code id for virtual platform in SGX code id + // table case QuoteFormat::insecure_virtual: case QuoteFormat::oe_sgx_v1: { tx.rw(Tables::NODE_CODE_IDS) - ->put(node_code_id, CodeStatus::ALLOWED_TO_JOIN); + ->put( + pal::SgxAttestationMeasurement(node_measurement), + CodeStatus::ALLOWED_TO_JOIN); break; } case QuoteFormat::amd_sev_snp_v1: { tx.rw(Tables::NODE_SNP_MEASUREMENTS) - ->put(node_code_id, CodeStatus::ALLOWED_TO_JOIN); + ->put( + pal::SnpAttestationMeasurement(node_measurement), + CodeStatus::ALLOWED_TO_JOIN); break; } default: diff --git a/tests/code_update.py b/tests/code_update.py index 6375a3d7f709..1ca15243dadd 100644 --- a/tests/code_update.py +++ b/tests/code_update.py @@ -16,7 +16,7 @@ from loguru import logger as LOG # Dummy code id used by virtual nodes -VIRTUAL_CODE_ID = "0" * 96 +VIRTUAL_CODE_ID = "0" * 64 @reqs.description("Verify node evidence") diff --git a/tests/infra/utils.py b/tests/infra/utils.py index 6e023f7ba672..db2b5c58fc96 100644 --- a/tests/infra/utils.py +++ b/tests/infra/utils.py @@ -28,4 +28,4 @@ def get_code_id( return lines[0].split("=")[1] else: # Virtual and SNP - return hashlib.sha384(lib_path.encode()).hexdigest() + return hashlib.sha256(lib_path.encode()).hexdigest()