From 0ea57ab820c32ab651c24c016e2d3379f3881624 Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Tue, 18 Feb 2025 17:00:41 +0000 Subject: [PATCH 01/29] Parse cpuid bytes of attestation --- include/ccf/pal/attestation_sev_snp.h | 5 ++++- js/ccf-app/src/global.ts | 3 +++ src/js/extensions/snp_attestation.cpp | 4 ++++ tests/npm-app/src/endpoints/snp_attestation.ts | 3 +++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/include/ccf/pal/attestation_sev_snp.h b/include/ccf/pal/attestation_sev_snp.h index f9c71facfaa5..75cea31c35e1 100644 --- a/include/ccf/pal/attestation_sev_snp.h +++ b/include/ccf/pal/attestation_sev_snp.h @@ -141,7 +141,10 @@ QPHfbkH0CyPfhl1jWhJFZasCAwEAAQ== 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 cpuid_fam_id; /* 0x188*/ + uint8_t cpuid_mod_id; /* 0x189 */ + uint8_t cpuid_step; /* 0x18A */ + uint8_t reserved1[21]; /* 0x18B */ uint8_t chip_id[64]; /* 0x1A0 */ struct TcbVersion committed_tcb; /* 0x1E0 */ uint8_t current_minor; /* 0x1E8 */ diff --git a/js/ccf-app/src/global.ts b/js/ccf-app/src/global.ts index c60439f9f23c..9497cb9ee6d6 100644 --- a/js/ccf-app/src/global.ts +++ b/js/ccf-app/src/global.ts @@ -789,6 +789,9 @@ export interface SnpAttestationResult { report_id: ArrayBuffer; report_id_ma: ArrayBuffer; reported_tcb: TcbVersion; + cpuid_fam_id: number; + cpuid_mod_id: number; + cpuid_step: number; chip_id: ArrayBuffer; committed_tcb: TcbVersion; current_minor: number; diff --git a/src/js/extensions/snp_attestation.cpp b/src/js/extensions/snp_attestation.cpp index 4e2365cf2464..8dafd1a59963 100644 --- a/src/js/extensions/snp_attestation.cpp +++ b/src/js/extensions/snp_attestation.cpp @@ -214,6 +214,10 @@ namespace ccf::js::extensions JS_CHECK_EXC(reported_tcb); JS_CHECK_SET(a.set("reported_tcb", std::move(reported_tcb))); + 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)); + 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))); diff --git a/tests/npm-app/src/endpoints/snp_attestation.ts b/tests/npm-app/src/endpoints/snp_attestation.ts index 339511f2e412..ea20d66fe39b 100644 --- a/tests/npm-app/src/endpoints/snp_attestation.ts +++ b/tests/npm-app/src/endpoints/snp_attestation.ts @@ -57,6 +57,9 @@ interface SnpAttestationResult { report_id: string; report_id_ma: string; reported_tcb: TcbVersion; + cpuid_fam_id: number; + cpuid_mod_id: number; + cpuid_step: number; chip_id: string; committed_tcb: TcbVersion; current_minor: number; From 1f3cb197b2d435c347c2fa62976f9b56bba50898 Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Fri, 21 Feb 2025 16:46:51 +0000 Subject: [PATCH 02/29] Check that the firmware version of SNP chips is correct --- include/ccf/pal/attestation.h | 78 +++++++++++++++++++++++++++ include/ccf/pal/attestation_sev_snp.h | 32 +++++++++++ 2 files changed, 110 insertions(+) diff --git a/include/ccf/pal/attestation.h b/include/ccf/pal/attestation.h index c75896e06e17..2eb608e920c6 100644 --- a/include/ccf/pal/attestation.h +++ b/include/ccf/pal/attestation.h @@ -98,6 +98,84 @@ namespace ccf::pal quote.vmpl)); } + // Check the FW and Microcode is sufficiently up to date: https://www.amd.com/en/resources/product-security/bulletin/amd-sb-3019.html + if (quote.version > 2) + { + pal::snp::AttestChipModel quote_chip_model = { + .family = quote.cpuid_fam_id, + .model = quote.cpuid_mod_id, + .stepping = quote.cpuid_step, + }; + + constexpr auto milan_chip_id = pal::snp::get_attest_chip_model( + {.stepping = 0x1, + .base_model = 0x1, + .base_family = 0xF, + .extended_model = 0x0, + .extended_family = 0x0A}); + if ( + quote_chip_model == milan_chip_id && + !(quote.reported_tcb.microcode >= 0xDB && + quote.reported_tcb.snp >= 0x18)) + { + throw std::logic_error(fmt::format( + "SEV-SNP: guest attestation report is not from a valid Milan " + "processor", + quote.reported_tcb)); + } + + constexpr auto milan_x_chip_id = pal::snp::get_attest_chip_model( + {.stepping = 0x2, + .base_model = 0x1, + .base_family = 0xF, + .extended_model = 0x0, + .extended_family = 0x0A}); + if ( + quote_chip_model == milan_x_chip_id && + !(quote.reported_tcb.microcode >= 0x44 && + quote.reported_tcb.snp >= 0x18)) + { + throw std::logic_error(fmt::format( + "SEV-SNP: guest attestation report is not from a valid Milan X" + "processor", + quote.reported_tcb)); + } + + constexpr auto genoa_chip_id = pal::snp::get_attest_chip_model( + {.stepping = 0x1, + .base_model = 0x1, + .base_family = 0xF, + .extended_model = 0x1, + .extended_family = 0x0A}); + if ( + quote_chip_model == genoa_chip_id && + !(quote.reported_tcb.microcode >= 0x54 && + quote.reported_tcb.snp >= 0x17)) + { + throw std::logic_error(fmt::format( + "SEV-SNP: guest attestation report is not from a valid Genoa " + "processor", + quote.reported_tcb)); + } + + constexpr auto genoa_x_chip_id = pal::snp::get_attest_chip_model( + {.stepping = 0x2, + .base_model = 0x1, + .base_family = 0xF, + .extended_model = 0x1, + .extended_family = 0x0A}); + if ( + quote_chip_model == milan_chip_id && + !(quote.reported_tcb.microcode >= 0x4F && + quote.reported_tcb.snp >= 0x17)) + { + throw std::logic_error(fmt::format( + "SEV-SNP: guest attestation report is not from a valid Genoa X " + "processor", + quote.reported_tcb)); + } + } + report_data = SnpAttestationReportData(quote.report_data); measurement = SnpAttestationMeasurement(quote.measurement); diff --git a/include/ccf/pal/attestation_sev_snp.h b/include/ccf/pal/attestation_sev_snp.h index 75cea31c35e1..94b1dfd1ecaf 100644 --- a/include/ccf/pal/attestation_sev_snp.h +++ b/include/ccf/pal/attestation_sev_snp.h @@ -264,4 +264,36 @@ QPHfbkH0CyPfhl1jWhJFZasCAwEAAQ== virtual ~AttestationInterface() = default; }; + + #pragma pack(push, 1) + struct CPUID + { + uint8_t stepping : 4; + uint8_t base_model : 4; + uint8_t base_family : 4; + uint8_t reserved : 4; + uint8_t extended_model : 4; + uint8_t extended_family : 8; + uint8_t reserved2 : 4; + }; + static_assert(sizeof(CPUID) == sizeof(uint32_t), "Can't cast CPUID to uint32_t"); + + struct AttestChipModel + { + uint8_t family; + uint8_t model; + uint8_t stepping; + + bool operator==(const AttestChipModel&) const = default; + }; + #pragma pack(pop) + + constexpr AttestChipModel get_attest_chip_model(const CPUID& cpuid) + { + AttestChipModel model; + model.family = cpuid.base_family + cpuid.extended_family; + model.model = (cpuid.extended_model << 4) | cpuid.base_model; + model.stepping = cpuid.stepping; + return model; + } } From 962b0f8ea05d1ca3381204b36e62df6f970c6865 Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Fri, 21 Feb 2025 17:02:36 +0000 Subject: [PATCH 03/29] Reformat --- include/ccf/pal/attestation.h | 15 ++++++--------- include/ccf/pal/attestation_sev_snp.h | 9 +++++---- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/include/ccf/pal/attestation.h b/include/ccf/pal/attestation.h index 2eb608e920c6..b58eec2d909e 100644 --- a/include/ccf/pal/attestation.h +++ b/include/ccf/pal/attestation.h @@ -98,7 +98,8 @@ namespace ccf::pal quote.vmpl)); } - // Check the FW and Microcode is sufficiently up to date: https://www.amd.com/en/resources/product-security/bulletin/amd-sb-3019.html + // Check the FW and Microcode is sufficiently up to date: + // https://www.amd.com/en/resources/product-security/bulletin/amd-sb-3019.html if (quote.version > 2) { pal::snp::AttestChipModel quote_chip_model = { @@ -120,8 +121,7 @@ namespace ccf::pal { throw std::logic_error(fmt::format( "SEV-SNP: guest attestation report is not from a valid Milan " - "processor", - quote.reported_tcb)); + "processor")); } constexpr auto milan_x_chip_id = pal::snp::get_attest_chip_model( @@ -137,8 +137,7 @@ namespace ccf::pal { throw std::logic_error(fmt::format( "SEV-SNP: guest attestation report is not from a valid Milan X" - "processor", - quote.reported_tcb)); + "processor")); } constexpr auto genoa_chip_id = pal::snp::get_attest_chip_model( @@ -154,8 +153,7 @@ namespace ccf::pal { throw std::logic_error(fmt::format( "SEV-SNP: guest attestation report is not from a valid Genoa " - "processor", - quote.reported_tcb)); + "processor")); } constexpr auto genoa_x_chip_id = pal::snp::get_attest_chip_model( @@ -171,8 +169,7 @@ namespace ccf::pal { throw std::logic_error(fmt::format( "SEV-SNP: guest attestation report is not from a valid Genoa X " - "processor", - quote.reported_tcb)); + "processor")); } } diff --git a/include/ccf/pal/attestation_sev_snp.h b/include/ccf/pal/attestation_sev_snp.h index 94b1dfd1ecaf..02ea0b8772c1 100644 --- a/include/ccf/pal/attestation_sev_snp.h +++ b/include/ccf/pal/attestation_sev_snp.h @@ -265,7 +265,7 @@ QPHfbkH0CyPfhl1jWhJFZasCAwEAAQ== virtual ~AttestationInterface() = default; }; - #pragma pack(push, 1) +#pragma pack(push, 1) struct CPUID { uint8_t stepping : 4; @@ -276,17 +276,18 @@ QPHfbkH0CyPfhl1jWhJFZasCAwEAAQ== uint8_t extended_family : 8; uint8_t reserved2 : 4; }; - static_assert(sizeof(CPUID) == sizeof(uint32_t), "Can't cast CPUID to uint32_t"); + static_assert( + sizeof(CPUID) == sizeof(uint32_t), "Can't cast CPUID to uint32_t"); struct AttestChipModel { uint8_t family; uint8_t model; uint8_t stepping; - + bool operator==(const AttestChipModel&) const = default; }; - #pragma pack(pop) +#pragma pack(pop) constexpr AttestChipModel get_attest_chip_model(const CPUID& cpuid) { From 86a375e8ac11dbfd0edddc1a1ba6f0e07075693e Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Fri, 21 Feb 2025 17:10:34 +0000 Subject: [PATCH 04/29] Add base case if nothing matches --- include/ccf/pal/attestation.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/ccf/pal/attestation.h b/include/ccf/pal/attestation.h index b58eec2d909e..1d42abab97dd 100644 --- a/include/ccf/pal/attestation.h +++ b/include/ccf/pal/attestation.h @@ -171,6 +171,12 @@ namespace ccf::pal "SEV-SNP: guest attestation report is not from a valid Genoa X " "processor")); } + + throw std::logic_error(fmt::format( + "SEV-SNP: Unrecognised processor family/model/stepping: {}/{}/{}", + quote_chip_model.family, + quote_chip_model.model, + quote_chip_model.stepping)); } report_data = SnpAttestationReportData(quote.report_data); From b149874220ec23cfe559bc69453c10dd1e7aa7a2 Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Fri, 21 Feb 2025 18:48:38 +0000 Subject: [PATCH 05/29] Move validation to tables --- include/ccf/node/quote.h | 2 + include/ccf/service/tables/tcb_verification.h | 17 ++++++ src/node/quote.cpp | 52 +++++++++++++++++++ src/node/rpc/node_frontend.h | 2 + src/service/internal_tables_access.h | 42 +++++++++++++++ src/service/network_tables.h | 5 +- 6 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 include/ccf/service/tables/tcb_verification.h diff --git a/include/ccf/node/quote.h b/include/ccf/node/quote.h index f499857d7ead..2689f3e9ff47 100644 --- a/include/ccf/node/quote.h +++ b/include/ccf/node/quote.h @@ -22,6 +22,8 @@ namespace ccf FailedInvalidHostData, FailedInvalidQuotedPublicKey, FailedUVMEndorsementsNotFound, + FailedInvalidCPUID, + FailedInvalidTcbVersion }; class AttestationProvider diff --git a/include/ccf/service/tables/tcb_verification.h b/include/ccf/service/tables/tcb_verification.h new file mode 100644 index 000000000000..2b9663372ef9 --- /dev/null +++ b/include/ccf/service/tables/tcb_verification.h @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the Apache 2.0 License. +#pragma once + +#include "ccf/ds/json.h" +#include "ccf/pal/attestation_sev_snp.h" +#include "ccf/service/map.h" + +namespace ccf +{ + using SnpTcbVersionMap = kv::Map; + + namespace Tables + { + static constexpr auto SNP_TCB_VERSIONS = "public:ccf.gov.nodes.snp_tcb_versions"; + } +} \ No newline at end of file diff --git a/src/node/quote.cpp b/src/node/quote.cpp index f821bab473a0..eb50b01351a0 100644 --- a/src/node/quote.cpp +++ b/src/node/quote.cpp @@ -6,6 +6,7 @@ #include "ccf/pal/attestation.h" #include "ccf/service/tables/code_id.h" #include "ccf/service/tables/snp_measurements.h" +#include "ccf/service/tables/tcb_verification.h" #include "ccf/service/tables/uvm_endorsements.h" #include "ccf/service/tables/virtual_measurements.h" #include "node/uvm_endorsements.h" @@ -231,6 +232,51 @@ namespace ccf return QuoteVerificationResult::Verified; } + QuoteVerificationResult verify_tcb_version_against_store( + ccf::kv::ReadOnlyTx& tx, const QuoteInfo& quote_info) + { + if (quote_info.format != QuoteFormat::amd_sev_snp_v1) + { + return QuoteVerificationResult::Verified; + } + + pal::PlatformAttestationMeasurement d = {}; + pal::PlatformAttestationReportData r = {}; + pal::verify_quote(quote_info, d, r); + auto attestation = + *reinterpret_cast(quote_info.quote.data()); + + if (attestation.version < 3) + { + // Necessary until all C-ACI servers are updated + return QuoteVerificationResult::Verified; + } + + pal::snp::AttestChipModel cpuid = { + .family = attestation.cpuid_fam_id, + .model = attestation.cpuid_mod_id, + .stepping = attestation.cpuid_step}; + + auto min_tcb_opt = + tx.ro(Tables::SNP_TCB_VERSIONS)->get(cpuid); + if (!min_tcb_opt.has_value()) + { + return QuoteVerificationResult::FailedInvalidCPUID; + } + + auto min_tcb = min_tcb_opt.value(); + + // only check snp and microcode as these are AMD controlled + if ( + min_tcb.snp > attestation.reported_tcb.snp || + min_tcb.microcode > attestation.reported_tcb.microcode) + { + return QuoteVerificationResult::FailedInvalidTcbVersion; + } + + return QuoteVerificationResult::Verified; + } + QuoteVerificationResult AttestationProvider::verify_quote_against_store( ccf::kv::ReadOnlyTx& tx, const QuoteInfo& quote_info, @@ -263,6 +309,12 @@ namespace ccf return rc; } + rc = verify_tcb_version_against_store(tx, quote_info); + if (rc != QuoteVerificationResult::Verified) + { + return rc; + } + return verify_quoted_node_public_key( expected_node_public_key_der, quoted_hash); } diff --git a/src/node/rpc/node_frontend.h b/src/node/rpc/node_frontend.h index f5e69e1dd0fc..09850a251e6f 100644 --- a/src/node/rpc/node_frontend.h +++ b/src/node/rpc/node_frontend.h @@ -1656,6 +1656,8 @@ namespace ccf } } + InternalTablesAccess::trust_static_snp_tcb_version(ctx.tx); + std::optional digest = ccf::get_create_tx_claims_digest(ctx.tx); if (digest.has_value()) diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index 72dbd998edd8..3c94e08cff5c 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -11,6 +11,7 @@ #include "ccf/service/tables/snp_measurements.h" #include "ccf/service/tables/users.h" #include "ccf/service/tables/virtual_measurements.h" +#include "ccf/service/tables/tcb_verification.h" #include "ccf/tx.h" #include "consensus/aft/raft_types.h" #include "crypto/openssl/cose_sign.h" @@ -822,6 +823,47 @@ namespace ccf {{uvm_endorsements->feed, {uvm_endorsements->svn}}}); } + static void trust_static_snp_tcb_version(ccf::kv::Tx& tx) + { + auto h = tx.wo(Tables::SNP_TCB_VERSIONS); + + constexpr auto milan_chip_id = pal::snp::get_attest_chip_model( + {.stepping = 0x1, + .base_model = 0x1, + .base_family = 0xF, + .extended_model = 0x0, + .extended_family = 0x0A}); + constexpr pal::snp::TcbVersion milan_tcb_version = {.microcode = 0xDB, .snp = 0x18}; + h->put(milan_chip_id, milan_tcb_version); + + constexpr auto milan_x_chip_id = pal::snp::get_attest_chip_model( + {.stepping = 0x2, + .base_model = 0x1, + .base_family = 0xF, + .extended_model = 0x0, + .extended_family = 0x0A}); + constexpr pal::snp::TcbVersion milan_x_tcb_version = {.microcode = 0x44, .snp = 0x18}; + h->put(milan_x_chip_id, milan_x_tcb_version); + + constexpr auto genoa_chip_id = pal::snp::get_attest_chip_model( + {.stepping = 0x1, + .base_model = 0x1, + .base_family = 0xF, + .extended_model = 0x1, + .extended_family = 0x0A}); + constexpr pal::snp::TcbVersion genoa_tcb_version = {.microcode = 0x54, .snp = 0x17}; + h->put(genoa_chip_id, genoa_tcb_version); + + constexpr auto genoa_x_chip_id = pal::snp::get_attest_chip_model( + {.stepping = 0x2, + .base_model = 0x1, + .base_family = 0xF, + .extended_model = 0x1, + .extended_family = 0x0A}); + constexpr pal::snp::TcbVersion genoa_x_tcb_version = {.microcode = 0x4F, .snp = 0x17}; + h->put(genoa_x_chip_id, genoa_x_tcb_version); + } + static void init_configuration( ccf::kv::Tx& tx, const ServiceConfiguration& configuration) { diff --git a/src/service/network_tables.h b/src/service/network_tables.h index a5576516a227..8cdfc1fc9735 100644 --- a/src/service/network_tables.h +++ b/src/service/network_tables.h @@ -18,6 +18,7 @@ #include "ccf/service/tables/proposals.h" #include "ccf/service/tables/service.h" #include "ccf/service/tables/snp_measurements.h" +#include "ccf/service/tables/tcb_verification.h" #include "ccf/service/tables/users.h" #include "ccf/service/tables/uvm_endorsements.h" #include "ccf/service/tables/virtual_measurements.h" @@ -96,6 +97,7 @@ namespace ccf const SnpMeasurements snp_measurements = {Tables::NODE_SNP_MEASUREMENTS}; const SNPUVMEndorsements snp_uvm_endorsements = { Tables::NODE_SNP_UVM_ENDORSEMENTS}; + const SNPTCBVersions snp_tcb_versions = {Tables::SNP_TCB_VERSIONS}; inline auto get_all_node_tables() const { @@ -108,7 +110,8 @@ namespace ccf virtual_measurements, host_data, snp_measurements, - snp_uvm_endorsements); + snp_uvm_endorsements, + snp_tcb_versions); } // From 1f3e567b15a9daa2084c03545fb41d6becd0fa8c Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Mon, 24 Feb 2025 15:23:53 +0000 Subject: [PATCH 06/29] Tmp --- include/ccf/pal/attestation_sev_snp.h | 5 +++++ src/node/rpc/member_frontend.h | 1 + src/node/rpc/node_frontend.h | 1 + src/service/internal_tables_access.h | 27 +++++++++++++++++++++++---- src/service/network_tables.h | 6 +++--- 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/include/ccf/pal/attestation_sev_snp.h b/include/ccf/pal/attestation_sev_snp.h index 02ea0b8772c1..6e1dd7dc0d2f 100644 --- a/include/ccf/pal/attestation_sev_snp.h +++ b/include/ccf/pal/attestation_sev_snp.h @@ -52,6 +52,8 @@ QPHfbkH0CyPfhl1jWhJFZasCAwEAAQ== static_assert( sizeof(TcbVersion) == sizeof(uint64_t), "Can't cast TcbVersion to uint64_t"); + DECLARE_JSON_TYPE(TcbVersion); + DECLARE_JSON_REQUIRED_FIELDS(TcbVersion, boot_loader, tee, snp, microcode); #pragma pack(push, 1) struct Signature @@ -265,6 +267,7 @@ QPHfbkH0CyPfhl1jWhJFZasCAwEAAQ== virtual ~AttestationInterface() = default; }; + static uint8_t MIN_TCB_VERIF_VERSION = 3; #pragma pack(push, 1) struct CPUID { @@ -288,6 +291,8 @@ QPHfbkH0CyPfhl1jWhJFZasCAwEAAQ== bool operator==(const AttestChipModel&) const = default; }; #pragma pack(pop) + DECLARE_JSON_TYPE(AttestChipModel); + DECLARE_JSON_REQUIRED_FIELDS(AttestChipModel, family, model, stepping); constexpr AttestChipModel get_attest_chip_model(const CPUID& cpuid) { diff --git a/src/node/rpc/member_frontend.h b/src/node/rpc/member_frontend.h index caddcf3a659a..d912d1c6609e 100644 --- a/src/node/rpc/member_frontend.h +++ b/src/node/rpc/member_frontend.h @@ -15,6 +15,7 @@ #include "ccf/service/tables/jwt.h" #include "ccf/service/tables/members.h" #include "ccf/service/tables/nodes.h" +#include "ccf/service/tables/tcb_verification.h" #include "frontend.h" #include "js/extensions/ccf/network.h" #include "js/extensions/ccf/node.h" diff --git a/src/node/rpc/node_frontend.h b/src/node/rpc/node_frontend.h index 09850a251e6f..9fada6b48186 100644 --- a/src/node/rpc/node_frontend.h +++ b/src/node/rpc/node_frontend.h @@ -1657,6 +1657,7 @@ namespace ccf } InternalTablesAccess::trust_static_snp_tcb_version(ctx.tx); + //InternalTablesAccess::trust_node_snp_tcb_version(ctx.tx, in.quote_info); std::optional digest = ccf::get_create_tx_claims_digest(ctx.tx); diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index 3c94e08cff5c..892981293803 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -833,7 +833,8 @@ namespace ccf .base_family = 0xF, .extended_model = 0x0, .extended_family = 0x0A}); - constexpr pal::snp::TcbVersion milan_tcb_version = {.microcode = 0xDB, .snp = 0x18}; + constexpr pal::snp::TcbVersion milan_tcb_version = { + .microcode = 0xDB, .snp = 0x18}; h->put(milan_chip_id, milan_tcb_version); constexpr auto milan_x_chip_id = pal::snp::get_attest_chip_model( @@ -842,7 +843,8 @@ namespace ccf .base_family = 0xF, .extended_model = 0x0, .extended_family = 0x0A}); - constexpr pal::snp::TcbVersion milan_x_tcb_version = {.microcode = 0x44, .snp = 0x18}; + constexpr pal::snp::TcbVersion milan_x_tcb_version = { + .microcode = 0x44, .snp = 0x18}; h->put(milan_x_chip_id, milan_x_tcb_version); constexpr auto genoa_chip_id = pal::snp::get_attest_chip_model( @@ -851,7 +853,8 @@ namespace ccf .base_family = 0xF, .extended_model = 0x1, .extended_family = 0x0A}); - constexpr pal::snp::TcbVersion genoa_tcb_version = {.microcode = 0x54, .snp = 0x17}; + constexpr pal::snp::TcbVersion genoa_tcb_version = { + .microcode = 0x54, .snp = 0x17}; h->put(genoa_chip_id, genoa_tcb_version); constexpr auto genoa_x_chip_id = pal::snp::get_attest_chip_model( @@ -860,10 +863,26 @@ namespace ccf .base_family = 0xF, .extended_model = 0x1, .extended_family = 0x0A}); - constexpr pal::snp::TcbVersion genoa_x_tcb_version = {.microcode = 0x4F, .snp = 0x17}; + constexpr pal::snp::TcbVersion genoa_x_tcb_version = { + .microcode = 0x4F, .snp = 0x17}; h->put(genoa_x_chip_id, genoa_x_tcb_version); } + static void trust_node_snp_tcb_version( + ccf::kv::Tx& tx, const pal::snp::Attestation& attestation) + { + if (attestation.version >= pal::snp::MIN_TCB_VERIF_VERSION) + { + pal::snp::AttestChipModel chip_id{ + .family = attestation.cpuid_fam_id, + .model = attestation.cpuid_mod_id, + .stepping = attestation.cpuid_step, + }; + auto h = tx.wo(Tables::SNP_TCB_VERSIONS); + h->put(chip_id, attestation.reported_tcb); + } + } + static void init_configuration( ccf::kv::Tx& tx, const ServiceConfiguration& configuration) { diff --git a/src/service/network_tables.h b/src/service/network_tables.h index 8cdfc1fc9735..87ae8c16b1a3 100644 --- a/src/service/network_tables.h +++ b/src/service/network_tables.h @@ -97,7 +97,7 @@ namespace ccf const SnpMeasurements snp_measurements = {Tables::NODE_SNP_MEASUREMENTS}; const SNPUVMEndorsements snp_uvm_endorsements = { Tables::NODE_SNP_UVM_ENDORSEMENTS}; - const SNPTCBVersions snp_tcb_versions = {Tables::SNP_TCB_VERSIONS}; + const SnpTcbVersionMap snp_tcb_versions = {Tables::SNP_TCB_VERSIONS}; inline auto get_all_node_tables() const { @@ -110,8 +110,8 @@ namespace ccf virtual_measurements, host_data, snp_measurements, - snp_uvm_endorsements, - snp_tcb_versions); + snp_uvm_endorsements + ); } // From 71888d8b987616f222a419733f27bca13331059f Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Mon, 24 Feb 2025 16:38:40 +0000 Subject: [PATCH 07/29] Add minimal hacky getter --- include/ccf/pal/attestation_sev_snp.h | 4 ++++ src/node/rpc/member_frontend.h | 4 +++- src/service/network_tables.h | 4 ++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/include/ccf/pal/attestation_sev_snp.h b/include/ccf/pal/attestation_sev_snp.h index 6e1dd7dc0d2f..9f21b6e428d3 100644 --- a/include/ccf/pal/attestation_sev_snp.h +++ b/include/ccf/pal/attestation_sev_snp.h @@ -289,6 +289,10 @@ QPHfbkH0CyPfhl1jWhJFZasCAwEAAQ== uint8_t stepping; bool operator==(const AttestChipModel&) const = default; + std::string hex_str() const + { + return fmt::format("{:02x}{:02x}{:02x}", family, model, stepping); + } }; #pragma pack(pop) DECLARE_JSON_TYPE(AttestChipModel); diff --git a/src/node/rpc/member_frontend.h b/src/node/rpc/member_frontend.h index d912d1c6609e..ff3c67bfc625 100644 --- a/src/node/rpc/member_frontend.h +++ b/src/node/rpc/member_frontend.h @@ -509,7 +509,9 @@ namespace ccf handle->foreach([&response_body](const auto& k, const auto& v) { if constexpr ( std::is_same_v || - pal::is_attestation_measurement::value) + pal::is_attestation_measurement::value || + std::is_same_v + ) { response_body[k.hex_str()] = v; } diff --git a/src/service/network_tables.h b/src/service/network_tables.h index 87ae8c16b1a3..a52cd5dc2c72 100644 --- a/src/service/network_tables.h +++ b/src/service/network_tables.h @@ -110,8 +110,8 @@ namespace ccf virtual_measurements, host_data, snp_measurements, - snp_uvm_endorsements - ); + snp_uvm_endorsements, + snp_tcb_versions); } // From f8196d82b5d263eda243f4c84121b2fc5de9a783 Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Mon, 24 Feb 2025 16:41:41 +0000 Subject: [PATCH 08/29] fmt --- include/ccf/service/tables/tcb_verification.h | 6 ++++-- src/node/rpc/member_frontend.h | 3 +-- src/service/internal_tables_access.h | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/include/ccf/service/tables/tcb_verification.h b/include/ccf/service/tables/tcb_verification.h index 2b9663372ef9..088057790de2 100644 --- a/include/ccf/service/tables/tcb_verification.h +++ b/include/ccf/service/tables/tcb_verification.h @@ -8,10 +8,12 @@ namespace ccf { - using SnpTcbVersionMap = kv::Map; + using SnpTcbVersionMap = + kv::Map; namespace Tables { - static constexpr auto SNP_TCB_VERSIONS = "public:ccf.gov.nodes.snp_tcb_versions"; + static constexpr auto SNP_TCB_VERSIONS = + "public:ccf.gov.nodes.snp_tcb_versions"; } } \ No newline at end of file diff --git a/src/node/rpc/member_frontend.h b/src/node/rpc/member_frontend.h index ff3c67bfc625..83f36a1fabd8 100644 --- a/src/node/rpc/member_frontend.h +++ b/src/node/rpc/member_frontend.h @@ -510,8 +510,7 @@ namespace ccf if constexpr ( std::is_same_v || pal::is_attestation_measurement::value || - std::is_same_v - ) + std::is_same_v) { response_body[k.hex_str()] = v; } diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index 892981293803..1c3be6ab4334 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -9,9 +9,9 @@ #include "ccf/service/tables/members.h" #include "ccf/service/tables/nodes.h" #include "ccf/service/tables/snp_measurements.h" +#include "ccf/service/tables/tcb_verification.h" #include "ccf/service/tables/users.h" #include "ccf/service/tables/virtual_measurements.h" -#include "ccf/service/tables/tcb_verification.h" #include "ccf/tx.h" #include "consensus/aft/raft_types.h" #include "crypto/openssl/cose_sign.h" From 38ca2f5129f49e3476ecfc93e5719d6c4160288a Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Mon, 24 Feb 2025 16:56:34 +0000 Subject: [PATCH 09/29] Trust host's tcb --- src/node/rpc/node_frontend.h | 2 +- src/service/internal_tables_access.h | 36 ++++++++++++++++++++-------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/node/rpc/node_frontend.h b/src/node/rpc/node_frontend.h index 9fada6b48186..7eee3aa73508 100644 --- a/src/node/rpc/node_frontend.h +++ b/src/node/rpc/node_frontend.h @@ -1657,7 +1657,7 @@ namespace ccf } InternalTablesAccess::trust_static_snp_tcb_version(ctx.tx); - //InternalTablesAccess::trust_node_snp_tcb_version(ctx.tx, in.quote_info); + InternalTablesAccess::trust_node_snp_tcb_version(ctx.tx, in.quote_info); std::optional digest = ccf::get_create_tx_claims_digest(ctx.tx); diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index 1c3be6ab4334..3a84b77ebeda 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -869,17 +869,33 @@ namespace ccf } static void trust_node_snp_tcb_version( - ccf::kv::Tx& tx, const pal::snp::Attestation& attestation) + ccf::kv::Tx& tx, cost QuoteInto& quote_info) { - if (attestation.version >= pal::snp::MIN_TCB_VERIF_VERSION) - { - pal::snp::AttestChipModel chip_id{ - .family = attestation.cpuid_fam_id, - .model = attestation.cpuid_mod_id, - .stepping = attestation.cpuid_step, - }; - auto h = tx.wo(Tables::SNP_TCB_VERSIONS); - h->put(chip_id, attestation.reported_tcb); + if (quote_info.format == QuoteFormat::amd_sev_snp_v1) + { + try + { + pal::PlatformAttestationMeasurement d = {}; + pal::PlatformAttestationReportData r = {}; + pal::verify_quote(quote_info, d, r); + auto attestation = *reinterpret_cast( + quote_info.quote.data()); + if (attestation.version >= pal::snp::MIN_TCB_VERIF_VERSION) + { + pal::snp::AttestChipModel chip_id{ + .family = attestation.cpuid_fam_id, + .model = attestation.cpuid_mod_id, + .stepping = attestation.cpuid_step, + }; + auto h = tx.wo(Tables::SNP_TCB_VERSIONS); + h->put(chip_id, attestation.reported_tcb); + } + } + catch (const std::exception& e) + { + LOG_FAIL_FMT("Failed to verify attestation report: {}", e.what()); + return std::nullopt; + } } } From 910a24a4dac7e4e855f0aaeb12e8428f901b73ea Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Mon, 24 Feb 2025 17:02:22 +0000 Subject: [PATCH 10/29] Use MIN_TCB_VERIF_VERSION --- include/ccf/pal/attestation.h | 2 +- src/node/quote.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/ccf/pal/attestation.h b/include/ccf/pal/attestation.h index 1d42abab97dd..f62644b7408d 100644 --- a/include/ccf/pal/attestation.h +++ b/include/ccf/pal/attestation.h @@ -100,7 +100,7 @@ namespace ccf::pal // Check the FW and Microcode is sufficiently up to date: // https://www.amd.com/en/resources/product-security/bulletin/amd-sb-3019.html - if (quote.version > 2) + if (quote.version > MIN_TCB_VERIF_VERSION) { pal::snp::AttestChipModel quote_chip_model = { .family = quote.cpuid_fam_id, diff --git a/src/node/quote.cpp b/src/node/quote.cpp index eb50b01351a0..89cc08733788 100644 --- a/src/node/quote.cpp +++ b/src/node/quote.cpp @@ -246,7 +246,7 @@ namespace ccf auto attestation = *reinterpret_cast(quote_info.quote.data()); - if (attestation.version < 3) + if (attestation.version < pal::snp::MIN_TCB_VERIF_VERSION) { // Necessary until all C-ACI servers are updated return QuoteVerificationResult::Verified; From 349e03ed2a8869130b39f283402b052ad7e805fa Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Mon, 24 Feb 2025 17:25:48 +0000 Subject: [PATCH 11/29] Make build --- include/ccf/node/quote.h | 4 ++ include/ccf/pal/attestation.h | 81 ---------------------------- src/node/quote.cpp | 23 ++++++++ src/node/rpc/node_frontend.h | 10 ++-- src/service/internal_tables_access.h | 44 +++++---------- 5 files changed, 48 insertions(+), 114 deletions(-) diff --git a/include/ccf/node/quote.h b/include/ccf/node/quote.h index 2689f3e9ff47..808c5e63821e 100644 --- a/include/ccf/node/quote.h +++ b/include/ccf/node/quote.h @@ -4,6 +4,7 @@ #include "ccf/ccf_deprecated.h" #include "ccf/ds/quote_info.h" +#include "ccf/pal/attestation_sev_snp.h" #include "ccf/pal/measurement.h" #include "ccf/service/tables/host_data.h" #include "ccf/tx.h" @@ -37,6 +38,9 @@ namespace ccf static std::optional get_host_data(const QuoteInfo& quote_info); + static std::optional get_snp_attestation( + const QuoteInfo& quote_info); + static QuoteVerificationResult verify_quote_against_store( ccf::kv::ReadOnlyTx& tx, const QuoteInfo& quote_info, diff --git a/include/ccf/pal/attestation.h b/include/ccf/pal/attestation.h index f62644b7408d..c75896e06e17 100644 --- a/include/ccf/pal/attestation.h +++ b/include/ccf/pal/attestation.h @@ -98,87 +98,6 @@ namespace ccf::pal quote.vmpl)); } - // Check the FW and Microcode is sufficiently up to date: - // https://www.amd.com/en/resources/product-security/bulletin/amd-sb-3019.html - if (quote.version > MIN_TCB_VERIF_VERSION) - { - pal::snp::AttestChipModel quote_chip_model = { - .family = quote.cpuid_fam_id, - .model = quote.cpuid_mod_id, - .stepping = quote.cpuid_step, - }; - - constexpr auto milan_chip_id = pal::snp::get_attest_chip_model( - {.stepping = 0x1, - .base_model = 0x1, - .base_family = 0xF, - .extended_model = 0x0, - .extended_family = 0x0A}); - if ( - quote_chip_model == milan_chip_id && - !(quote.reported_tcb.microcode >= 0xDB && - quote.reported_tcb.snp >= 0x18)) - { - throw std::logic_error(fmt::format( - "SEV-SNP: guest attestation report is not from a valid Milan " - "processor")); - } - - constexpr auto milan_x_chip_id = pal::snp::get_attest_chip_model( - {.stepping = 0x2, - .base_model = 0x1, - .base_family = 0xF, - .extended_model = 0x0, - .extended_family = 0x0A}); - if ( - quote_chip_model == milan_x_chip_id && - !(quote.reported_tcb.microcode >= 0x44 && - quote.reported_tcb.snp >= 0x18)) - { - throw std::logic_error(fmt::format( - "SEV-SNP: guest attestation report is not from a valid Milan X" - "processor")); - } - - constexpr auto genoa_chip_id = pal::snp::get_attest_chip_model( - {.stepping = 0x1, - .base_model = 0x1, - .base_family = 0xF, - .extended_model = 0x1, - .extended_family = 0x0A}); - if ( - quote_chip_model == genoa_chip_id && - !(quote.reported_tcb.microcode >= 0x54 && - quote.reported_tcb.snp >= 0x17)) - { - throw std::logic_error(fmt::format( - "SEV-SNP: guest attestation report is not from a valid Genoa " - "processor")); - } - - constexpr auto genoa_x_chip_id = pal::snp::get_attest_chip_model( - {.stepping = 0x2, - .base_model = 0x1, - .base_family = 0xF, - .extended_model = 0x1, - .extended_family = 0x0A}); - if ( - quote_chip_model == milan_chip_id && - !(quote.reported_tcb.microcode >= 0x4F && - quote.reported_tcb.snp >= 0x17)) - { - throw std::logic_error(fmt::format( - "SEV-SNP: guest attestation report is not from a valid Genoa X " - "processor")); - } - - throw std::logic_error(fmt::format( - "SEV-SNP: Unrecognised processor family/model/stepping: {}/{}/{}", - quote_chip_model.family, - quote_chip_model.model, - quote_chip_model.stepping)); - } - report_data = SnpAttestationReportData(quote.report_data); measurement = SnpAttestationMeasurement(quote.measurement); diff --git a/src/node/quote.cpp b/src/node/quote.cpp index 89cc08733788..322d668221d5 100644 --- a/src/node/quote.cpp +++ b/src/node/quote.cpp @@ -139,6 +139,29 @@ namespace ccf return measurement; } + std::optional AttestationProvider::get_snp_attestation( + const QuoteInfo& quote_info) + { + if (quote_info.format != QuoteFormat::amd_sev_snp_v1) + { + return std::nullopt; + } + try + { + pal::PlatformAttestationMeasurement d = {}; + pal::PlatformAttestationReportData r = {}; + pal::verify_quote(quote_info, d, r); + auto attestation = *reinterpret_cast( + quote_info.quote.data()); + return attestation; + } + catch (const std::exception& e) + { + LOG_FAIL_FMT("Failed to verify local attestation report: {}", e.what()); + return std::nullopt; + } + } + std::optional AttestationProvider::get_host_data( const QuoteInfo& quote_info) { diff --git a/src/node/rpc/node_frontend.h b/src/node/rpc/node_frontend.h index 7eee3aa73508..b9e89178c514 100644 --- a/src/node/rpc/node_frontend.h +++ b/src/node/rpc/node_frontend.h @@ -1621,6 +1621,8 @@ namespace ccf ctx.tx, in.measurement, in.quote_info.format); } + InternalTablesAccess::trust_static_snp_tcb_version(ctx.tx); + switch (in.quote_info.format) { case QuoteFormat::insecure_virtual: @@ -1647,6 +1649,11 @@ namespace ccf InternalTablesAccess::trust_node_uvm_endorsements( ctx.tx, in.snp_uvm_endorsements); + + auto attestation = + AttestationProvider::get_snp_attestation(in.quote_info).value(); + InternalTablesAccess::trust_node_snp_tcb_version( + ctx.tx, attestation); break; } @@ -1656,9 +1663,6 @@ namespace ccf } } - InternalTablesAccess::trust_static_snp_tcb_version(ctx.tx); - InternalTablesAccess::trust_node_snp_tcb_version(ctx.tx, in.quote_info); - std::optional digest = ccf::get_create_tx_claims_digest(ctx.tx); if (digest.has_value()) diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index 3a84b77ebeda..41a2e3cb01b1 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -834,7 +834,7 @@ namespace ccf .extended_model = 0x0, .extended_family = 0x0A}); constexpr pal::snp::TcbVersion milan_tcb_version = { - .microcode = 0xDB, .snp = 0x18}; + .snp = 0x18, .microcode = 0xDB}; h->put(milan_chip_id, milan_tcb_version); constexpr auto milan_x_chip_id = pal::snp::get_attest_chip_model( @@ -844,7 +844,7 @@ namespace ccf .extended_model = 0x0, .extended_family = 0x0A}); constexpr pal::snp::TcbVersion milan_x_tcb_version = { - .microcode = 0x44, .snp = 0x18}; + .snp = 0x18, .microcode = 0x44}; h->put(milan_x_chip_id, milan_x_tcb_version); constexpr auto genoa_chip_id = pal::snp::get_attest_chip_model( @@ -854,7 +854,7 @@ namespace ccf .extended_model = 0x1, .extended_family = 0x0A}); constexpr pal::snp::TcbVersion genoa_tcb_version = { - .microcode = 0x54, .snp = 0x17}; + .snp = 0x17, .microcode = 0x54}; h->put(genoa_chip_id, genoa_tcb_version); constexpr auto genoa_x_chip_id = pal::snp::get_attest_chip_model( @@ -864,38 +864,22 @@ namespace ccf .extended_model = 0x1, .extended_family = 0x0A}); constexpr pal::snp::TcbVersion genoa_x_tcb_version = { - .microcode = 0x4F, .snp = 0x17}; + .snp = 0x17, .microcode = 0x4F}; h->put(genoa_x_chip_id, genoa_x_tcb_version); } static void trust_node_snp_tcb_version( - ccf::kv::Tx& tx, cost QuoteInto& quote_info) + ccf::kv::Tx& tx, pal::snp::Attestation& attestation) { - if (quote_info.format == QuoteFormat::amd_sev_snp_v1) - { - try - { - pal::PlatformAttestationMeasurement d = {}; - pal::PlatformAttestationReportData r = {}; - pal::verify_quote(quote_info, d, r); - auto attestation = *reinterpret_cast( - quote_info.quote.data()); - if (attestation.version >= pal::snp::MIN_TCB_VERIF_VERSION) - { - pal::snp::AttestChipModel chip_id{ - .family = attestation.cpuid_fam_id, - .model = attestation.cpuid_mod_id, - .stepping = attestation.cpuid_step, - }; - auto h = tx.wo(Tables::SNP_TCB_VERSIONS); - h->put(chip_id, attestation.reported_tcb); - } - } - catch (const std::exception& e) - { - LOG_FAIL_FMT("Failed to verify attestation report: {}", e.what()); - return std::nullopt; - } + if (attestation.version >= pal::snp::MIN_TCB_VERIF_VERSION) + { + pal::snp::AttestChipModel chip_id{ + .family = attestation.cpuid_fam_id, + .model = attestation.cpuid_mod_id, + .stepping = attestation.cpuid_step, + }; + auto h = tx.wo(Tables::SNP_TCB_VERSIONS); + h->put(chip_id, attestation.reported_tcb); } } From 1ebec9f323d84f27c6a285f230243fe21a8c516b Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Tue, 25 Feb 2025 11:33:11 +0000 Subject: [PATCH 12/29] Use hex represntation of AttestChipModel as key --- include/ccf/pal/attestation_sev_snp.h | 34 ++++++++++++++++--- include/ccf/service/tables/tcb_verification.h | 2 +- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/include/ccf/pal/attestation_sev_snp.h b/include/ccf/pal/attestation_sev_snp.h index 9f21b6e428d3..35d825b255a8 100644 --- a/include/ccf/pal/attestation_sev_snp.h +++ b/include/ccf/pal/attestation_sev_snp.h @@ -291,13 +291,11 @@ QPHfbkH0CyPfhl1jWhJFZasCAwEAAQ== bool operator==(const AttestChipModel&) const = default; std::string hex_str() const { - return fmt::format("{:02x}{:02x}{:02x}", family, model, stepping); + auto begin = reinterpret_cast(this); + return ccf::ds::to_hex(begin, begin + sizeof(AttestChipModel)); } }; #pragma pack(pop) - DECLARE_JSON_TYPE(AttestChipModel); - DECLARE_JSON_REQUIRED_FIELDS(AttestChipModel, family, model, stepping); - constexpr AttestChipModel get_attest_chip_model(const CPUID& cpuid) { AttestChipModel model; @@ -307,3 +305,31 @@ QPHfbkH0CyPfhl1jWhJFZasCAwEAAQ== return model; } } + +namespace ccf::kv::serialisers +{ + // Use hex string to ensure uniformity between the endpoint perspective and + // the kv's key + template<> + struct BlitSerialiser + { + static SerialisedEntry to_serialised( + const ccf::pal::snp::AttestChipModel& chip) + { + auto hex_str = chip.hex_str(); + return SerialisedEntry(hex_str.begin(), hex_str.end()); + } + + static ccf::pal::snp::AttestChipModel from_serialised( + const SerialisedEntry& data) + { + ccf::pal::snp::AttestChipModel ret; + auto buf_ptr = reinterpret_cast(&ret); + ccf::ds::from_hex( + std::string(data.data(), data.end()), + buf_ptr, + buf_ptr + sizeof(ccf::pal::snp::AttestChipModel)); + return ret; + } + }; +} \ No newline at end of file diff --git a/include/ccf/service/tables/tcb_verification.h b/include/ccf/service/tables/tcb_verification.h index 088057790de2..c4bd985244a6 100644 --- a/include/ccf/service/tables/tcb_verification.h +++ b/include/ccf/service/tables/tcb_verification.h @@ -9,7 +9,7 @@ namespace ccf { using SnpTcbVersionMap = - kv::Map; + ServiceMap; namespace Tables { From 012462c0bc5d568924e5a660ef97bf6dbdaf8ce3 Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Wed, 26 Feb 2025 10:30:58 +0000 Subject: [PATCH 13/29] AttestChipModel should have json representation --- include/ccf/pal/attestation_sev_snp.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/ccf/pal/attestation_sev_snp.h b/include/ccf/pal/attestation_sev_snp.h index 35d825b255a8..77413974a46a 100644 --- a/include/ccf/pal/attestation_sev_snp.h +++ b/include/ccf/pal/attestation_sev_snp.h @@ -296,6 +296,8 @@ QPHfbkH0CyPfhl1jWhJFZasCAwEAAQ== } }; #pragma pack(pop) + DECLARE_JSON_TYPE(AttestChipModel); + DECLARE_JSON_REQUIRED_FIELDS(AttestChipModel, family, model, stepping); constexpr AttestChipModel get_attest_chip_model(const CPUID& cpuid) { AttestChipModel model; @@ -310,7 +312,7 @@ namespace ccf::kv::serialisers { // Use hex string to ensure uniformity between the endpoint perspective and // the kv's key - template<> + template <> struct BlitSerialiser { static SerialisedEntry to_serialised( From 7f909c1b36178dcccde2badc904410cc94161e93 Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Wed, 26 Feb 2025 11:32:35 +0000 Subject: [PATCH 14/29] 0 out remainder of TcbVersion --- src/service/internal_tables_access.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index 41a2e3cb01b1..0a930eb8c25b 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -834,7 +834,7 @@ namespace ccf .extended_model = 0x0, .extended_family = 0x0A}); constexpr pal::snp::TcbVersion milan_tcb_version = { - .snp = 0x18, .microcode = 0xDB}; + .boot_loader = 0, .tee = 0, .snp = 0x18, .microcode = 0xDB}; h->put(milan_chip_id, milan_tcb_version); constexpr auto milan_x_chip_id = pal::snp::get_attest_chip_model( @@ -844,7 +844,7 @@ namespace ccf .extended_model = 0x0, .extended_family = 0x0A}); constexpr pal::snp::TcbVersion milan_x_tcb_version = { - .snp = 0x18, .microcode = 0x44}; + .boot_loader = 0, .tee = 0, .snp = 0x18, .microcode = 0x44}; h->put(milan_x_chip_id, milan_x_tcb_version); constexpr auto genoa_chip_id = pal::snp::get_attest_chip_model( @@ -854,7 +854,7 @@ namespace ccf .extended_model = 0x1, .extended_family = 0x0A}); constexpr pal::snp::TcbVersion genoa_tcb_version = { - .snp = 0x17, .microcode = 0x54}; + .boot_loader = 0, .tee = 0, .snp = 0x17, .microcode = 0x54}; h->put(genoa_chip_id, genoa_tcb_version); constexpr auto genoa_x_chip_id = pal::snp::get_attest_chip_model( @@ -864,7 +864,7 @@ namespace ccf .extended_model = 0x1, .extended_family = 0x0A}); constexpr pal::snp::TcbVersion genoa_x_tcb_version = { - .snp = 0x17, .microcode = 0x4F}; + .boot_loader = 0, .tee = 0, .snp = 0x17, .microcode = 0x4F}; h->put(genoa_x_chip_id, genoa_x_tcb_version); } From b8fa350f27d42a97bb30f1a0b148583518ec7c37 Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Wed, 26 Feb 2025 13:31:15 +0000 Subject: [PATCH 15/29] Initialize members --- src/service/internal_tables_access.h | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index 0a930eb8c25b..e7632578e9a9 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -831,40 +831,48 @@ namespace ccf {.stepping = 0x1, .base_model = 0x1, .base_family = 0xF, + .reserved = 0, .extended_model = 0x0, - .extended_family = 0x0A}); + .extended_family = 0x0A, + .reserved2 = 0}); constexpr pal::snp::TcbVersion milan_tcb_version = { - .boot_loader = 0, .tee = 0, .snp = 0x18, .microcode = 0xDB}; + .boot_loader = 0, .tee = 0, .reserved={0}, .snp = 0x18, .microcode = 0xDB}; h->put(milan_chip_id, milan_tcb_version); constexpr auto milan_x_chip_id = pal::snp::get_attest_chip_model( {.stepping = 0x2, .base_model = 0x1, .base_family = 0xF, + .reserved = 0, .extended_model = 0x0, - .extended_family = 0x0A}); + .extended_family = 0x0A, + .reserved2 = 0}); constexpr pal::snp::TcbVersion milan_x_tcb_version = { - .boot_loader = 0, .tee = 0, .snp = 0x18, .microcode = 0x44}; + .boot_loader = 0, .tee = 0, .reserved={0}, .snp = 0x18, .microcode = 0x44}; h->put(milan_x_chip_id, milan_x_tcb_version); constexpr auto genoa_chip_id = pal::snp::get_attest_chip_model( {.stepping = 0x1, .base_model = 0x1, .base_family = 0xF, + .reserved = 0, .extended_model = 0x1, - .extended_family = 0x0A}); + .extended_family = 0x0A, + .reserved2 = 0}); constexpr pal::snp::TcbVersion genoa_tcb_version = { - .boot_loader = 0, .tee = 0, .snp = 0x17, .microcode = 0x54}; + .boot_loader = 0, .tee = 0, .reserved={0}, .snp = 0x17, .microcode = 0x54}; h->put(genoa_chip_id, genoa_tcb_version); constexpr auto genoa_x_chip_id = pal::snp::get_attest_chip_model( {.stepping = 0x2, .base_model = 0x1, .base_family = 0xF, + .reserved = 0, .extended_model = 0x1, - .extended_family = 0x0A}); + .extended_family = 0x0A, + .reserved2 = 0}); constexpr pal::snp::TcbVersion genoa_x_tcb_version = { - .boot_loader = 0, .tee = 0, .snp = 0x17, .microcode = 0x4F}; + .boot_loader = 0, .tee = 0, .reserved={0}, .snp = 0x17, .microcode = 0x4F}; h->put(genoa_x_chip_id, genoa_x_tcb_version); } From 0881d2848eafe2a938788ea562a90cfbf37cf388 Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Fri, 28 Feb 2025 10:13:55 +0000 Subject: [PATCH 16/29] Add test for verify_tcb_against_store --- CMakeLists.txt | 5 ++ .../test/snp_attestation_verification.cpp | 66 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 src/node/test/snp_attestation_verification.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index feac4e021395..cbfc65838f27 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -654,6 +654,11 @@ if(BUILD_TESTS) snp_ioctl_test ${CMAKE_CURRENT_SOURCE_DIR}/src/pal/test/snp_ioctl_test.cpp ) + + add_unit_test( + snp_attestation_verification + ${CMAKE_CURRENT_SOURCE_DIR}/src/node/test/snp_attestation_verification.cpp + ) endif() add_unit_test(map_test ${CMAKE_CURRENT_SOURCE_DIR}/src/ds/test/map_test.cpp) diff --git a/src/node/test/snp_attestation_verification.cpp b/src/node/test/snp_attestation_verification.cpp new file mode 100644 index 000000000000..3ad343d012f3 --- /dev/null +++ b/src/node/test/snp_attestation_verification.cpp @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the Apache 2.0 License. + +#include "ccf/pal/attestation_sev_snp.h" +#include "ccf/pal/snp_ioctl.h" +#include "ccf/service/tables/tcb_verification.h" +#include "ccf/node/quote.h" +#include "kv/store.h" +#include "kv/test/null_encryptor.h" +#include "pal/quote_generation.h" +#include "ccf/ds/quote_info.h" + +#include +#include +#include + +#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN +#include + +using namespace ccf; + +TEST_CASE("E2E") +{ + kv::Store kv_store; + auto encryptor = std::make_shared(); + kv_store.set_encryptor(encryptor); + + auto tx = kv_store.create_tx(); + auto h = tx.wo(Tables::SNP_TCB_VERSIONS); + + REQUIRE(pal::snp::is_sev_snp()); + + auto attest_intf = pal::snp::get_attestation({}); + QuoteInfo quote_info = {}; + quote_info.format = QuoteFormat::amd_sev_snp_v1; + quote_info.quote = attest_intf->get_raw(); + + auto rc = AttestationProvider::verify_tcb_version_against_store(tx, quote_info); + CHECK_EQ(rc, QuoteVerificationResult::FailedInvalidCPUID); + + auto attestation = attest_intf->get(); + + // populate store with info from current attestation + auto current_tcb = attestation.reported_tcb; + pal::snp::AttestChipModel chip_id{ + .family = attestation.cpuid_fam_id, + .model = attestation.cpuid_mod_id, + .stepping = attestation.cpuid_step, + }; + h->put(chip_id, current_tcb); + + rc = AttestationProvider::verify_tcb_version_against_store(tx, quote_info); + CHECK_EQ(rc, QuoteVerificationResult::Verified); + + auto new_tcb_snp = current_tcb; + new_tcb_snp.snp += 1; + h->put(chip_id, new_tcb_snp); + rc = AttestationProvider::verify_tcb_version_against_store(tx, quote_info); + CHECK_EQ(rc, QuoteVerificationResult::FailedInvalidTcbVersion); + + auto new_tcb_microcode = current_tcb; + new_tcb_microcode.microcode += 1; + h->put(chip_id, new_tcb_microcode); + rc = AttestationProvider::verify_tcb_version_against_store(tx, quote_info); + CHECK_EQ(rc, QuoteVerificationResult::FailedInvalidTcbVersion); +} \ No newline at end of file From 85dcbeda8825591db7240796f20be6dd257f5e25 Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Fri, 28 Feb 2025 13:57:42 +0000 Subject: [PATCH 17/29] Move get_tcb_version out of attestation_provider. FIx import of logger in snp_ioctlX.h --- CMakeLists.txt | 4 ++++ include/ccf/node/quote.h | 3 +++ include/ccf/pal/snp_ioctl5.h | 1 + include/ccf/pal/snp_ioctl6.h | 1 + src/node/test/snp_attestation_verification.cpp | 8 ++++---- src/pal/test/snp_ioctl_test.cpp | 1 - 6 files changed, 13 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cbfc65838f27..6aef741a9273 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -658,6 +658,10 @@ if(BUILD_TESTS) add_unit_test( snp_attestation_verification ${CMAKE_CURRENT_SOURCE_DIR}/src/node/test/snp_attestation_verification.cpp + ${CCF_DIR}/src/node/quote.cpp + ) + target_link_libraries( + snp_attestation_verification PRIVATE ccf_kv.host ) endif() diff --git a/include/ccf/node/quote.h b/include/ccf/node/quote.h index 808c5e63821e..f5b2d73e55ef 100644 --- a/include/ccf/node/quote.h +++ b/include/ccf/node/quote.h @@ -47,4 +47,7 @@ namespace ccf const std::vector& expected_node_public_key_der, pal::PlatformAttestationMeasurement& measurement); }; + QuoteVerificationResult verify_tcb_version_against_store( + ccf::kv::ReadOnlyTx& tx, const QuoteInfo& quote_info); + } \ No newline at end of file diff --git a/include/ccf/pal/snp_ioctl5.h b/include/ccf/pal/snp_ioctl5.h index a40251bb5f25..2306e694b53c 100644 --- a/include/ccf/pal/snp_ioctl5.h +++ b/include/ccf/pal/snp_ioctl5.h @@ -3,6 +3,7 @@ #pragma once #include "ccf/pal/attestation_sev_snp.h" +#include "ccf/ds/logger.h" #include #include diff --git a/include/ccf/pal/snp_ioctl6.h b/include/ccf/pal/snp_ioctl6.h index 26fe4cc858c1..e0e742ce14a7 100644 --- a/include/ccf/pal/snp_ioctl6.h +++ b/include/ccf/pal/snp_ioctl6.h @@ -2,6 +2,7 @@ // Licensed under the Apache 2.0 License. #pragma once +#include "ccf/ds/logger.h" #include "ccf/pal/attestation_sev_snp.h" #include diff --git a/src/node/test/snp_attestation_verification.cpp b/src/node/test/snp_attestation_verification.cpp index 3ad343d012f3..835415c1012d 100644 --- a/src/node/test/snp_attestation_verification.cpp +++ b/src/node/test/snp_attestation_verification.cpp @@ -35,7 +35,7 @@ TEST_CASE("E2E") quote_info.format = QuoteFormat::amd_sev_snp_v1; quote_info.quote = attest_intf->get_raw(); - auto rc = AttestationProvider::verify_tcb_version_against_store(tx, quote_info); + auto rc = verify_tcb_version_against_store(tx, quote_info); CHECK_EQ(rc, QuoteVerificationResult::FailedInvalidCPUID); auto attestation = attest_intf->get(); @@ -49,18 +49,18 @@ TEST_CASE("E2E") }; h->put(chip_id, current_tcb); - rc = AttestationProvider::verify_tcb_version_against_store(tx, quote_info); + rc = verify_tcb_version_against_store(tx, quote_info); CHECK_EQ(rc, QuoteVerificationResult::Verified); auto new_tcb_snp = current_tcb; new_tcb_snp.snp += 1; h->put(chip_id, new_tcb_snp); - rc = AttestationProvider::verify_tcb_version_against_store(tx, quote_info); + rc = verify_tcb_version_against_store(tx, quote_info); CHECK_EQ(rc, QuoteVerificationResult::FailedInvalidTcbVersion); auto new_tcb_microcode = current_tcb; new_tcb_microcode.microcode += 1; h->put(chip_id, new_tcb_microcode); - rc = AttestationProvider::verify_tcb_version_against_store(tx, quote_info); + rc = verify_tcb_version_against_store(tx, quote_info); CHECK_EQ(rc, QuoteVerificationResult::FailedInvalidTcbVersion); } \ No newline at end of file diff --git a/src/pal/test/snp_ioctl_test.cpp b/src/pal/test/snp_ioctl_test.cpp index bfb4cf8b1f75..9711ea5a3390 100644 --- a/src/pal/test/snp_ioctl_test.cpp +++ b/src/pal/test/snp_ioctl_test.cpp @@ -2,7 +2,6 @@ // Licensed under the Apache 2.0 License. #include "ccf/crypto/symmetric_key.h" -#include "ccf/ds/logger.h" #include "ccf/pal/snp_ioctl.h" #include "crypto/openssl/hash.h" From 0ab54d7fc1304b2741e50d65d57db64924d4874b Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Fri, 28 Feb 2025 14:01:10 +0000 Subject: [PATCH 18/29] poke the birds --- .snpcc_canary | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.snpcc_canary b/.snpcc_canary index 2a5a8091c3ff..d73609c32873 100644 --- a/.snpcc_canary +++ b/.snpcc_canary @@ -1,5 +1,5 @@ - ___ ___ ___ \/ - (. =) Y (0 0) (x X) Y (vv) - O \ o | / | + ___ ___ ___ \_/ + (. =) Y (0 0) (x X) Y (___) + O \ o | / | /-xXx--//-----x=x--/-xXx--/---x-/--->>>--/ ... From b639a71477e8b1bf04adc3a2f3c5e0fe223ab59a Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Fri, 28 Feb 2025 14:07:31 +0000 Subject: [PATCH 19/29] Reformat --- CMakeLists.txt | 4 +--- include/ccf/pal/snp_ioctl5.h | 2 +- .../test/snp_attestation_verification.cpp | 4 ++-- src/service/internal_tables_access.h | 24 +++++++++++++++---- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6aef741a9273..c386a6844b63 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -660,9 +660,7 @@ if(BUILD_TESTS) ${CMAKE_CURRENT_SOURCE_DIR}/src/node/test/snp_attestation_verification.cpp ${CCF_DIR}/src/node/quote.cpp ) - target_link_libraries( - snp_attestation_verification PRIVATE ccf_kv.host - ) + target_link_libraries(snp_attestation_verification PRIVATE ccf_kv.host) endif() add_unit_test(map_test ${CMAKE_CURRENT_SOURCE_DIR}/src/ds/test/map_test.cpp) diff --git a/include/ccf/pal/snp_ioctl5.h b/include/ccf/pal/snp_ioctl5.h index 2306e694b53c..972e273994a1 100644 --- a/include/ccf/pal/snp_ioctl5.h +++ b/include/ccf/pal/snp_ioctl5.h @@ -2,8 +2,8 @@ // Licensed under the Apache 2.0 License. #pragma once -#include "ccf/pal/attestation_sev_snp.h" #include "ccf/ds/logger.h" +#include "ccf/pal/attestation_sev_snp.h" #include #include diff --git a/src/node/test/snp_attestation_verification.cpp b/src/node/test/snp_attestation_verification.cpp index 835415c1012d..0c5937a92d63 100644 --- a/src/node/test/snp_attestation_verification.cpp +++ b/src/node/test/snp_attestation_verification.cpp @@ -1,14 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the Apache 2.0 License. +#include "ccf/ds/quote_info.h" +#include "ccf/node/quote.h" #include "ccf/pal/attestation_sev_snp.h" #include "ccf/pal/snp_ioctl.h" #include "ccf/service/tables/tcb_verification.h" -#include "ccf/node/quote.h" #include "kv/store.h" #include "kv/test/null_encryptor.h" #include "pal/quote_generation.h" -#include "ccf/ds/quote_info.h" #include #include diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index e7632578e9a9..1c82ab70223a 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -836,7 +836,11 @@ namespace ccf .extended_family = 0x0A, .reserved2 = 0}); constexpr pal::snp::TcbVersion milan_tcb_version = { - .boot_loader = 0, .tee = 0, .reserved={0}, .snp = 0x18, .microcode = 0xDB}; + .boot_loader = 0, + .tee = 0, + .reserved = {0}, + .snp = 0x18, + .microcode = 0xDB}; h->put(milan_chip_id, milan_tcb_version); constexpr auto milan_x_chip_id = pal::snp::get_attest_chip_model( @@ -848,7 +852,11 @@ namespace ccf .extended_family = 0x0A, .reserved2 = 0}); constexpr pal::snp::TcbVersion milan_x_tcb_version = { - .boot_loader = 0, .tee = 0, .reserved={0}, .snp = 0x18, .microcode = 0x44}; + .boot_loader = 0, + .tee = 0, + .reserved = {0}, + .snp = 0x18, + .microcode = 0x44}; h->put(milan_x_chip_id, milan_x_tcb_version); constexpr auto genoa_chip_id = pal::snp::get_attest_chip_model( @@ -860,7 +868,11 @@ namespace ccf .extended_family = 0x0A, .reserved2 = 0}); constexpr pal::snp::TcbVersion genoa_tcb_version = { - .boot_loader = 0, .tee = 0, .reserved={0}, .snp = 0x17, .microcode = 0x54}; + .boot_loader = 0, + .tee = 0, + .reserved = {0}, + .snp = 0x17, + .microcode = 0x54}; h->put(genoa_chip_id, genoa_tcb_version); constexpr auto genoa_x_chip_id = pal::snp::get_attest_chip_model( @@ -872,7 +884,11 @@ namespace ccf .extended_family = 0x0A, .reserved2 = 0}); constexpr pal::snp::TcbVersion genoa_x_tcb_version = { - .boot_loader = 0, .tee = 0, .reserved={0}, .snp = 0x17, .microcode = 0x4F}; + .boot_loader = 0, + .tee = 0, + .reserved = {0}, + .snp = 0x17, + .microcode = 0x4F}; h->put(genoa_x_chip_id, genoa_x_tcb_version); } From 824346a8e6a4eb7637c80d8ae7f555e5f197200e Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Mon, 3 Mar 2025 17:44:42 +0000 Subject: [PATCH 20/29] Store cpuid in kv. Check attestation against it. --- CMakeLists.txt | 7 -- include/ccf/pal/attestation_sev_snp.h | 49 +++++++------- include/ccf/service/tables/tcb_verification.h | 2 +- src/node/gov/handlers/service_state.h | 13 ++++ src/node/quote.cpp | 19 ++++-- src/node/rpc/member_frontend.h | 2 +- src/node/rpc/node_frontend.h | 3 + .../test/snp_attestation_verification.cpp | 66 ------------------- src/pal/quote_generation.h | 1 + src/service/internal_tables_access.h | 28 +++----- 10 files changed, 67 insertions(+), 123 deletions(-) delete mode 100644 src/node/test/snp_attestation_verification.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c386a6844b63..feac4e021395 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -654,13 +654,6 @@ if(BUILD_TESTS) snp_ioctl_test ${CMAKE_CURRENT_SOURCE_DIR}/src/pal/test/snp_ioctl_test.cpp ) - - add_unit_test( - snp_attestation_verification - ${CMAKE_CURRENT_SOURCE_DIR}/src/node/test/snp_attestation_verification.cpp - ${CCF_DIR}/src/node/quote.cpp - ) - target_link_libraries(snp_attestation_verification PRIVATE ccf_kv.host) endif() add_unit_test(map_test ${CMAKE_CURRENT_SOURCE_DIR}/src/ds/test/map_test.cpp) diff --git a/include/ccf/pal/attestation_sev_snp.h b/include/ccf/pal/attestation_sev_snp.h index 77413974a46a..89f8ed93c330 100644 --- a/include/ccf/pal/attestation_sev_snp.h +++ b/include/ccf/pal/attestation_sev_snp.h @@ -6,6 +6,7 @@ #include "ccf/pal/attestation_sev_snp_endorsements.h" #include "ccf/pal/measurement.h" #include "ccf/pal/report_data.h" +#include "ccf/pal/hardware_info.h" #include #include @@ -278,33 +279,33 @@ QPHfbkH0CyPfhl1jWhJFZasCAwEAAQ== uint8_t extended_model : 4; uint8_t extended_family : 8; uint8_t reserved2 : 4; - }; - static_assert( - sizeof(CPUID) == sizeof(uint32_t), "Can't cast CPUID to uint32_t"); - struct AttestChipModel - { - uint8_t family; - uint8_t model; - uint8_t stepping; - - bool operator==(const AttestChipModel&) const = default; + bool operator==(const CPUID&) const = default; std::string hex_str() const { auto begin = reinterpret_cast(this); - return ccf::ds::to_hex(begin, begin + sizeof(AttestChipModel)); + return ccf::ds::to_hex(begin, begin + sizeof(CPUID)); + } + inline uint8_t get_family_id() const + { + return this->base_family + this->extended_family; + } + inline uint8_t get_model_id() const + { + return (this->extended_model << 4) | this->base_model; } }; + static_assert( + sizeof(CPUID) == sizeof(uint32_t), "Can't cast CPUID to uint32_t"); #pragma pack(pop) - DECLARE_JSON_TYPE(AttestChipModel); - DECLARE_JSON_REQUIRED_FIELDS(AttestChipModel, family, model, stepping); - constexpr AttestChipModel get_attest_chip_model(const CPUID& cpuid) + DECLARE_JSON_TYPE(CPUID); + DECLARE_JSON_REQUIRED_FIELDS(CPUID, stepping, base_model, base_family, extended_model, extended_family); + + static CPUID get_cpuid() { - AttestChipModel model; - model.family = cpuid.base_family + cpuid.extended_family; - model.model = (cpuid.extended_model << 4) | cpuid.base_model; - model.stepping = cpuid.stepping; - return model; + CpuidInfo cpuid_info{}; + cpuid(&cpuid_info, 1, 0); + return *reinterpret_cast(&cpuid_info.eax); // TODO validate } } @@ -313,24 +314,24 @@ namespace ccf::kv::serialisers // Use hex string to ensure uniformity between the endpoint perspective and // the kv's key template <> - struct BlitSerialiser + struct BlitSerialiser { static SerialisedEntry to_serialised( - const ccf::pal::snp::AttestChipModel& chip) + const ccf::pal::snp::CPUID& chip) { auto hex_str = chip.hex_str(); return SerialisedEntry(hex_str.begin(), hex_str.end()); } - static ccf::pal::snp::AttestChipModel from_serialised( + static ccf::pal::snp::CPUID from_serialised( const SerialisedEntry& data) { - ccf::pal::snp::AttestChipModel ret; + ccf::pal::snp::CPUID ret; auto buf_ptr = reinterpret_cast(&ret); ccf::ds::from_hex( std::string(data.data(), data.end()), buf_ptr, - buf_ptr + sizeof(ccf::pal::snp::AttestChipModel)); + buf_ptr + sizeof(ccf::pal::snp::CPUID)); return ret; } }; diff --git a/include/ccf/service/tables/tcb_verification.h b/include/ccf/service/tables/tcb_verification.h index c4bd985244a6..d1ca42b64ef9 100644 --- a/include/ccf/service/tables/tcb_verification.h +++ b/include/ccf/service/tables/tcb_verification.h @@ -9,7 +9,7 @@ namespace ccf { using SnpTcbVersionMap = - ServiceMap; + ServiceMap; namespace Tables { diff --git a/src/node/gov/handlers/service_state.h b/src/node/gov/handlers/service_state.h index 7fe0e3f6a636..c3f5c4909ac9 100644 --- a/src/node/gov/handlers/service_state.h +++ b/src/node/gov/handlers/service_state.h @@ -615,6 +615,19 @@ namespace ccf::gov::endpoints }); snp_policy["uvmEndorsements"] = snp_endorsements; + auto snp_tcb_versions = nlohmann::json::object(); + auto tcb_versions_handle = + ctx.tx.template ro( + ccf::Tables::SNP_TCB_VERSIONS); + tcb_versions_handle->foreach( + [&snp_tcb_versions]( + const pal::snp::CPUID& cpuid, + const pal::snp::TcbVersion& tcb_version) { + snp_tcb_versions[cpuid.hex_str()] = tcb_version; + return true; + }); + snp_policy["tcbVersions"] = snp_tcb_versions; + response_body["snp"] = snp_policy; } diff --git a/src/node/quote.cpp b/src/node/quote.cpp index 322d668221d5..f70cf71fb9b8 100644 --- a/src/node/quote.cpp +++ b/src/node/quote.cpp @@ -275,13 +275,20 @@ namespace ccf return QuoteVerificationResult::Verified; } - pal::snp::AttestChipModel cpuid = { - .family = attestation.cpuid_fam_id, - .model = attestation.cpuid_mod_id, - .stepping = attestation.cpuid_step}; + std::optional min_tcb_opt = std::nullopt; + + auto h = tx.ro(Tables::SNP_TCB_VERSIONS); + // expensive but there should not be many entries in this table only one per cpu + h->foreach([&min_tcb_opt, &attestation](const pal::snp::CPUID& cpuid, const pal::snp::TcbVersion& v) { + if (cpuid.get_family_id() == attestation.cpuid_fam_id && + cpuid.get_model_id() == attestation.cpuid_mod_id && + cpuid.stepping == attestation.cpuid_step) { + min_tcb_opt = v; + return false; + } + return true; + }); - auto min_tcb_opt = - tx.ro(Tables::SNP_TCB_VERSIONS)->get(cpuid); if (!min_tcb_opt.has_value()) { return QuoteVerificationResult::FailedInvalidCPUID; diff --git a/src/node/rpc/member_frontend.h b/src/node/rpc/member_frontend.h index 83f36a1fabd8..12acba229b78 100644 --- a/src/node/rpc/member_frontend.h +++ b/src/node/rpc/member_frontend.h @@ -510,7 +510,7 @@ namespace ccf if constexpr ( std::is_same_v || pal::is_attestation_measurement::value || - std::is_same_v) + std::is_same_v) { response_body[k.hex_str()] = v; } diff --git a/src/node/rpc/node_frontend.h b/src/node/rpc/node_frontend.h index b9e89178c514..b345a8439875 100644 --- a/src/node/rpc/node_frontend.h +++ b/src/node/rpc/node_frontend.h @@ -1638,6 +1638,9 @@ namespace ccf LOG_FAIL_FMT("Unable to extract host data from virtual quote"); } break; + + InternalTablesAccess::trust_static_snp_tcb_version(ctx.tx); + } case QuoteFormat::amd_sev_snp_v1: diff --git a/src/node/test/snp_attestation_verification.cpp b/src/node/test/snp_attestation_verification.cpp deleted file mode 100644 index 0c5937a92d63..000000000000 --- a/src/node/test/snp_attestation_verification.cpp +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the Apache 2.0 License. - -#include "ccf/ds/quote_info.h" -#include "ccf/node/quote.h" -#include "ccf/pal/attestation_sev_snp.h" -#include "ccf/pal/snp_ioctl.h" -#include "ccf/service/tables/tcb_verification.h" -#include "kv/store.h" -#include "kv/test/null_encryptor.h" -#include "pal/quote_generation.h" - -#include -#include -#include - -#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN -#include - -using namespace ccf; - -TEST_CASE("E2E") -{ - kv::Store kv_store; - auto encryptor = std::make_shared(); - kv_store.set_encryptor(encryptor); - - auto tx = kv_store.create_tx(); - auto h = tx.wo(Tables::SNP_TCB_VERSIONS); - - REQUIRE(pal::snp::is_sev_snp()); - - auto attest_intf = pal::snp::get_attestation({}); - QuoteInfo quote_info = {}; - quote_info.format = QuoteFormat::amd_sev_snp_v1; - quote_info.quote = attest_intf->get_raw(); - - auto rc = verify_tcb_version_against_store(tx, quote_info); - CHECK_EQ(rc, QuoteVerificationResult::FailedInvalidCPUID); - - auto attestation = attest_intf->get(); - - // populate store with info from current attestation - auto current_tcb = attestation.reported_tcb; - pal::snp::AttestChipModel chip_id{ - .family = attestation.cpuid_fam_id, - .model = attestation.cpuid_mod_id, - .stepping = attestation.cpuid_step, - }; - h->put(chip_id, current_tcb); - - rc = verify_tcb_version_against_store(tx, quote_info); - CHECK_EQ(rc, QuoteVerificationResult::Verified); - - auto new_tcb_snp = current_tcb; - new_tcb_snp.snp += 1; - h->put(chip_id, new_tcb_snp); - rc = verify_tcb_version_against_store(tx, quote_info); - CHECK_EQ(rc, QuoteVerificationResult::FailedInvalidTcbVersion); - - auto new_tcb_microcode = current_tcb; - new_tcb_microcode.microcode += 1; - h->put(chip_id, new_tcb_microcode); - rc = verify_tcb_version_against_store(tx, quote_info); - CHECK_EQ(rc, QuoteVerificationResult::FailedInvalidTcbVersion); -} \ No newline at end of file diff --git a/src/pal/quote_generation.h b/src/pal/quote_generation.h index 347da0fff7c3..e81736e51ac8 100644 --- a/src/pal/quote_generation.h +++ b/src/pal/quote_generation.h @@ -4,6 +4,7 @@ #include "ccf/crypto/hash_provider.h" #include "ds/files.h" +#include "ccf/pal/attestation.h" #include #include diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index 1c82ab70223a..9e1e06e31b60 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -827,14 +827,13 @@ namespace ccf { auto h = tx.wo(Tables::SNP_TCB_VERSIONS); - constexpr auto milan_chip_id = pal::snp::get_attest_chip_model( - {.stepping = 0x1, + constexpr pal::snp::CPUID milan_chip_id {.stepping = 0x1, .base_model = 0x1, .base_family = 0xF, .reserved = 0, .extended_model = 0x0, .extended_family = 0x0A, - .reserved2 = 0}); + .reserved2 = 0}; constexpr pal::snp::TcbVersion milan_tcb_version = { .boot_loader = 0, .tee = 0, @@ -843,14 +842,13 @@ namespace ccf .microcode = 0xDB}; h->put(milan_chip_id, milan_tcb_version); - constexpr auto milan_x_chip_id = pal::snp::get_attest_chip_model( - {.stepping = 0x2, + constexpr pal::snp::CPUID milan_x_chip_id {.stepping = 0x2, .base_model = 0x1, .base_family = 0xF, .reserved = 0, .extended_model = 0x0, .extended_family = 0x0A, - .reserved2 = 0}); + .reserved2 = 0}; constexpr pal::snp::TcbVersion milan_x_tcb_version = { .boot_loader = 0, .tee = 0, @@ -859,14 +857,13 @@ namespace ccf .microcode = 0x44}; h->put(milan_x_chip_id, milan_x_tcb_version); - constexpr auto genoa_chip_id = pal::snp::get_attest_chip_model( - {.stepping = 0x1, + constexpr pal::snp::CPUID genoa_chip_id {.stepping = 0x1, .base_model = 0x1, .base_family = 0xF, .reserved = 0, .extended_model = 0x1, .extended_family = 0x0A, - .reserved2 = 0}); + .reserved2 = 0}; constexpr pal::snp::TcbVersion genoa_tcb_version = { .boot_loader = 0, .tee = 0, @@ -875,14 +872,13 @@ namespace ccf .microcode = 0x54}; h->put(genoa_chip_id, genoa_tcb_version); - constexpr auto genoa_x_chip_id = pal::snp::get_attest_chip_model( - {.stepping = 0x2, + constexpr pal::snp::CPUID genoa_x_chip_id {.stepping = 0x2, .base_model = 0x1, .base_family = 0xF, .reserved = 0, .extended_model = 0x1, .extended_family = 0x0A, - .reserved2 = 0}); + .reserved2 = 0}; constexpr pal::snp::TcbVersion genoa_x_tcb_version = { .boot_loader = 0, .tee = 0, @@ -897,13 +893,9 @@ namespace ccf { if (attestation.version >= pal::snp::MIN_TCB_VERIF_VERSION) { - pal::snp::AttestChipModel chip_id{ - .family = attestation.cpuid_fam_id, - .model = attestation.cpuid_mod_id, - .stepping = attestation.cpuid_step, - }; + auto cpuid = pal::snp::get_cpuid(); auto h = tx.wo(Tables::SNP_TCB_VERSIONS); - h->put(chip_id, attestation.reported_tcb); + h->put(cpuid, attestation.reported_tcb); } } From 3e294e5dca71ea00beb81c1cfd5d795ffac34f9f Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Tue, 4 Mar 2025 11:55:50 +0000 Subject: [PATCH 21/29] Fix cpuid request --- include/ccf/pal/attestation_sev_snp.h | 19 ++++++++++++++----- src/service/internal_tables_access.h | 23 +++++++++++++++++++---- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/include/ccf/pal/attestation_sev_snp.h b/include/ccf/pal/attestation_sev_snp.h index 89f8ed93c330..c3ca7ac6fecd 100644 --- a/include/ccf/pal/attestation_sev_snp.h +++ b/include/ccf/pal/attestation_sev_snp.h @@ -295,17 +295,26 @@ QPHfbkH0CyPfhl1jWhJFZasCAwEAAQ== return (this->extended_model << 4) | this->base_model; } }; - static_assert( - sizeof(CPUID) == sizeof(uint32_t), "Can't cast CPUID to uint32_t"); #pragma pack(pop) DECLARE_JSON_TYPE(CPUID); DECLARE_JSON_REQUIRED_FIELDS(CPUID, stepping, base_model, base_family, extended_model, extended_family); + static_assert( + sizeof(CPUID) == sizeof(uint32_t), "Can't cast CPUID to uint32_t"); + + union UnionedCPUID{ + uint32_t eax; + CPUID cpuid; + }; static CPUID get_cpuid() { - CpuidInfo cpuid_info{}; - cpuid(&cpuid_info, 1, 0); - return *reinterpret_cast(&cpuid_info.eax); // TODO validate + UnionedCPUID cpuid_eax; + cpuid_eax.eax = 0; + asm volatile( + "cpuid" + : "=a"(cpuid_eax.eax) + : "a"(1)); + return cpuid_eax.cpuid; } } diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index 9e1e06e31b60..9c2437c3f832 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -891,12 +891,27 @@ namespace ccf static void trust_node_snp_tcb_version( ccf::kv::Tx& tx, pal::snp::Attestation& attestation) { - if (attestation.version >= pal::snp::MIN_TCB_VERIF_VERSION) + // Fall back to statically configured tcb versions + auto cpuid = pal::snp::get_cpuid(); + if (attestation.version < pal::snp::MIN_TCB_VERIF_VERSION) { - auto cpuid = pal::snp::get_cpuid(); - auto h = tx.wo(Tables::SNP_TCB_VERSIONS); - h->put(cpuid, attestation.reported_tcb); + LOG_FAIL_FMT( + "Snp attestation version ({}) too old", attestation.version); + trust_static_snp_tcb_version(tx); + return; + } + if ( + cpuid->get_family_id() != attestation.cpuid_fam_id || + cpuid->get_model_id() != attestation.cpuid_mod_id || + cpuid->stepping != attestation.cpuid_step) + { + LOG_FAIL_FMT( + "Snp cpuid does not match attestation cpuid ({} != {}, {}, {})", cpuid->hex_str(), attestation.cpuid_fam_id, attestation.cpuid_mod_id, attestation.cpuid_step); + trust_static_snp_tcb_version(tx); + return; } + auto h = tx.wo(Tables::SNP_TCB_VERSIONS); + h->put(cpuid, attestation.reported_tcb); } static void init_configuration( From 7b136f036e33e22f9ce20edf4b5e7dde6962067d Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Tue, 4 Mar 2025 12:02:41 +0000 Subject: [PATCH 22/29] fmt --- include/ccf/pal/attestation_sev_snp.h | 18 ++--- include/ccf/service/tables/tcb_verification.h | 3 +- src/node/quote.cpp | 17 +++-- src/node/rpc/node_frontend.h | 1 - src/pal/quote_generation.h | 2 +- src/service/internal_tables_access.h | 68 +++++++++++-------- 6 files changed, 57 insertions(+), 52 deletions(-) diff --git a/include/ccf/pal/attestation_sev_snp.h b/include/ccf/pal/attestation_sev_snp.h index c3ca7ac6fecd..d374fdd19ffe 100644 --- a/include/ccf/pal/attestation_sev_snp.h +++ b/include/ccf/pal/attestation_sev_snp.h @@ -6,7 +6,6 @@ #include "ccf/pal/attestation_sev_snp_endorsements.h" #include "ccf/pal/measurement.h" #include "ccf/pal/report_data.h" -#include "ccf/pal/hardware_info.h" #include #include @@ -297,11 +296,13 @@ QPHfbkH0CyPfhl1jWhJFZasCAwEAAQ== }; #pragma pack(pop) DECLARE_JSON_TYPE(CPUID); - DECLARE_JSON_REQUIRED_FIELDS(CPUID, stepping, base_model, base_family, extended_model, extended_family); + DECLARE_JSON_REQUIRED_FIELDS( + CPUID, stepping, base_model, base_family, extended_model, extended_family); static_assert( sizeof(CPUID) == sizeof(uint32_t), "Can't cast CPUID to uint32_t"); - union UnionedCPUID{ + union UnionedCPUID + { uint32_t eax; CPUID cpuid; }; @@ -310,10 +311,7 @@ QPHfbkH0CyPfhl1jWhJFZasCAwEAAQ== { UnionedCPUID cpuid_eax; cpuid_eax.eax = 0; - asm volatile( - "cpuid" - : "=a"(cpuid_eax.eax) - : "a"(1)); + asm volatile("cpuid" : "=a"(cpuid_eax.eax) : "a"(1)); return cpuid_eax.cpuid; } } @@ -325,15 +323,13 @@ namespace ccf::kv::serialisers template <> struct BlitSerialiser { - static SerialisedEntry to_serialised( - const ccf::pal::snp::CPUID& chip) + static SerialisedEntry to_serialised(const ccf::pal::snp::CPUID& chip) { auto hex_str = chip.hex_str(); return SerialisedEntry(hex_str.begin(), hex_str.end()); } - static ccf::pal::snp::CPUID from_serialised( - const SerialisedEntry& data) + static ccf::pal::snp::CPUID from_serialised(const SerialisedEntry& data) { ccf::pal::snp::CPUID ret; auto buf_ptr = reinterpret_cast(&ret); diff --git a/include/ccf/service/tables/tcb_verification.h b/include/ccf/service/tables/tcb_verification.h index d1ca42b64ef9..74b57730f9b3 100644 --- a/include/ccf/service/tables/tcb_verification.h +++ b/include/ccf/service/tables/tcb_verification.h @@ -8,8 +8,7 @@ namespace ccf { - using SnpTcbVersionMap = - ServiceMap; + using SnpTcbVersionMap = ServiceMap; namespace Tables { diff --git a/src/node/quote.cpp b/src/node/quote.cpp index f70cf71fb9b8..9383663710fb 100644 --- a/src/node/quote.cpp +++ b/src/node/quote.cpp @@ -276,16 +276,19 @@ namespace ccf } std::optional min_tcb_opt = std::nullopt; - + auto h = tx.ro(Tables::SNP_TCB_VERSIONS); - // expensive but there should not be many entries in this table only one per cpu - h->foreach([&min_tcb_opt, &attestation](const pal::snp::CPUID& cpuid, const pal::snp::TcbVersion& v) { - if (cpuid.get_family_id() == attestation.cpuid_fam_id && - cpuid.get_model_id() == attestation.cpuid_mod_id && - cpuid.stepping == attestation.cpuid_step) { + // expensive but there should not be many entries + h->foreach([&min_tcb_opt, &attestation]( + const pal::snp::CPUID& cpuid, const pal::snp::TcbVersion& v) { + if ( + cpuid.get_family_id() == attestation.cpuid_fam_id && + cpuid.get_model_id() == attestation.cpuid_mod_id && + cpuid.stepping == attestation.cpuid_step) + { min_tcb_opt = v; return false; - } + } return true; }); diff --git a/src/node/rpc/node_frontend.h b/src/node/rpc/node_frontend.h index b345a8439875..670f2d3f86f8 100644 --- a/src/node/rpc/node_frontend.h +++ b/src/node/rpc/node_frontend.h @@ -1640,7 +1640,6 @@ namespace ccf break; InternalTablesAccess::trust_static_snp_tcb_version(ctx.tx); - } case QuoteFormat::amd_sev_snp_v1: diff --git a/src/pal/quote_generation.h b/src/pal/quote_generation.h index e81736e51ac8..f5a7aa6ce06d 100644 --- a/src/pal/quote_generation.h +++ b/src/pal/quote_generation.h @@ -3,8 +3,8 @@ #pragma once #include "ccf/crypto/hash_provider.h" -#include "ds/files.h" #include "ccf/pal/attestation.h" +#include "ds/files.h" #include #include diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index 9c2437c3f832..5c9dd6884be8 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -827,13 +827,14 @@ namespace ccf { auto h = tx.wo(Tables::SNP_TCB_VERSIONS); - constexpr pal::snp::CPUID milan_chip_id {.stepping = 0x1, - .base_model = 0x1, - .base_family = 0xF, - .reserved = 0, - .extended_model = 0x0, - .extended_family = 0x0A, - .reserved2 = 0}; + constexpr pal::snp::CPUID milan_chip_id{ + .stepping = 0x1, + .base_model = 0x1, + .base_family = 0xF, + .reserved = 0, + .extended_model = 0x0, + .extended_family = 0x0A, + .reserved2 = 0}; constexpr pal::snp::TcbVersion milan_tcb_version = { .boot_loader = 0, .tee = 0, @@ -842,13 +843,14 @@ namespace ccf .microcode = 0xDB}; h->put(milan_chip_id, milan_tcb_version); - constexpr pal::snp::CPUID milan_x_chip_id {.stepping = 0x2, - .base_model = 0x1, - .base_family = 0xF, - .reserved = 0, - .extended_model = 0x0, - .extended_family = 0x0A, - .reserved2 = 0}; + constexpr pal::snp::CPUID milan_x_chip_id{ + .stepping = 0x2, + .base_model = 0x1, + .base_family = 0xF, + .reserved = 0, + .extended_model = 0x0, + .extended_family = 0x0A, + .reserved2 = 0}; constexpr pal::snp::TcbVersion milan_x_tcb_version = { .boot_loader = 0, .tee = 0, @@ -857,13 +859,14 @@ namespace ccf .microcode = 0x44}; h->put(milan_x_chip_id, milan_x_tcb_version); - constexpr pal::snp::CPUID genoa_chip_id {.stepping = 0x1, - .base_model = 0x1, - .base_family = 0xF, - .reserved = 0, - .extended_model = 0x1, - .extended_family = 0x0A, - .reserved2 = 0}; + constexpr pal::snp::CPUID genoa_chip_id{ + .stepping = 0x1, + .base_model = 0x1, + .base_family = 0xF, + .reserved = 0, + .extended_model = 0x1, + .extended_family = 0x0A, + .reserved2 = 0}; constexpr pal::snp::TcbVersion genoa_tcb_version = { .boot_loader = 0, .tee = 0, @@ -872,13 +875,14 @@ namespace ccf .microcode = 0x54}; h->put(genoa_chip_id, genoa_tcb_version); - constexpr pal::snp::CPUID genoa_x_chip_id {.stepping = 0x2, - .base_model = 0x1, - .base_family = 0xF, - .reserved = 0, - .extended_model = 0x1, - .extended_family = 0x0A, - .reserved2 = 0}; + constexpr pal::snp::CPUID genoa_x_chip_id{ + .stepping = 0x2, + .base_model = 0x1, + .base_family = 0xF, + .reserved = 0, + .extended_model = 0x1, + .extended_family = 0x0A, + .reserved2 = 0}; constexpr pal::snp::TcbVersion genoa_x_tcb_version = { .boot_loader = 0, .tee = 0, @@ -891,7 +895,7 @@ namespace ccf static void trust_node_snp_tcb_version( ccf::kv::Tx& tx, pal::snp::Attestation& attestation) { - // Fall back to statically configured tcb versions + // Fall back to statically configured tcb versions auto cpuid = pal::snp::get_cpuid(); if (attestation.version < pal::snp::MIN_TCB_VERIF_VERSION) { @@ -906,7 +910,11 @@ namespace ccf cpuid->stepping != attestation.cpuid_step) { LOG_FAIL_FMT( - "Snp cpuid does not match attestation cpuid ({} != {}, {}, {})", cpuid->hex_str(), attestation.cpuid_fam_id, attestation.cpuid_mod_id, attestation.cpuid_step); + "Snp cpuid does not match attestation cpuid ({} != {}, {}, {})", + cpuid->hex_str(), + attestation.cpuid_fam_id, + attestation.cpuid_mod_id, + attestation.cpuid_step); trust_static_snp_tcb_version(tx); return; } From 8512235d27c59a1de22024cbc0a1ba67588eeefb Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Tue, 4 Mar 2025 16:16:24 +0000 Subject: [PATCH 23/29] Add governance action for tcb_versions --- include/ccf/js/extensions/ccf/converters.h | 1 + include/ccf/service/tables/tcb_verification.h | 2 +- samples/constitutions/default/actions.js | 37 +++++++++++++++++++ src/js/extensions/ccf/converters.cpp | 24 ++++++++++++ tests/e2e_operations.py | 32 ++++++++++++++++ 5 files changed, 95 insertions(+), 1 deletion(-) diff --git a/include/ccf/js/extensions/ccf/converters.h b/include/ccf/js/extensions/ccf/converters.h index 8af18051fe52..df58b3b4ac36 100644 --- a/include/ccf/js/extensions/ccf/converters.h +++ b/include/ccf/js/extensions/ccf/converters.h @@ -15,6 +15,7 @@ namespace ccf::js::extensions * - ccf.bufToJsonCompatible * * - ccf.pemToId + * - ccf.jsonToTcbVersion * * - ccf.enableUntrustedDateTime * - ccf.enableMetricsLogging diff --git a/include/ccf/service/tables/tcb_verification.h b/include/ccf/service/tables/tcb_verification.h index 74b57730f9b3..f28861677355 100644 --- a/include/ccf/service/tables/tcb_verification.h +++ b/include/ccf/service/tables/tcb_verification.h @@ -13,6 +13,6 @@ namespace ccf namespace Tables { static constexpr auto SNP_TCB_VERSIONS = - "public:ccf.gov.nodes.snp_tcb_versions"; + "public:ccf.gov.nodes.snp.tcb_versions"; } } \ No newline at end of file diff --git a/samples/constitutions/default/actions.js b/samples/constitutions/default/actions.js index 0dd2e8ada4c9..e5ff321b6a33 100644 --- a/samples/constitutions/default/actions.js +++ b/samples/constitutions/default/actions.js @@ -1096,6 +1096,30 @@ const actions = new Map([ }, ), ], + [ + "add_snp_tcb_version", + new Action( + function(args) { + checkType(args.cpuid, "string", "cpuid"); + checkLength(hexStrToBuf(args.cpuid), 4, 4, "cpuid"); + + checkType(args.tcb_version, "object", "tcb_version"); + checkType(args.tcb_version?.boot_loader, "number", "tcb_version.boot_loader"); + checkType(args.tcb_version?.tee, "number", "tcb_version.tee"); + checkType(args.tcb_version?.snp, "number", "tcb_version.snp"); + checkType(args.tcb_version?.microcode, "number", "tcb_version.microcode"); + }, + function (args) { + ccf.kv["public:ccf.gov.nodes.snp.tcb_versions"].set( + hexStrToBuf(args.cpuid), + ccf.jsonToSnpTcbVersion(args.tcb_version), + ); + + // Is this required? + //invalidateOtherOpenProposals(proposalId); + } + ) + ], [ "remove_snp_host_data", new Action( @@ -1151,6 +1175,19 @@ const actions = new Map([ }, ), ], + [ + "remove_snp_tcb_version", + new Action( + function(args) { + checkType(args.cpuid, "string", "cpuid"); + checkLength(hexStrToBuf(args.cpuid), 4, 4, "cpuid"); + }, + function (args) { + const cpuid = hexStrToBuf(args.cpuid); + ccf.kv["public:ccf.gov.nodes.snp.tcb_versions"].delete(cpuid); + } + ) + ], [ "set_node_data", new Action( diff --git a/src/js/extensions/ccf/converters.cpp b/src/js/extensions/ccf/converters.cpp index b53649e9e7bf..5a8784be1f52 100644 --- a/src/js/extensions/ccf/converters.cpp +++ b/src/js/extensions/ccf/converters.cpp @@ -10,6 +10,8 @@ #include "ccf/version.h" #include "js/checks.h" #include "node/rpc/jwt_management.h" +#include "ccf/pal/attestation_sev_snp.h" +#include #include @@ -191,6 +193,26 @@ namespace ccf::js::extensions ctx, "Failed to parse PEM: %s", exc.what()); } } + + JSValue js_json_to_tcb_version( + JSContext* ctx, JSValueConst, int argc, JSValueConst* argv) + { + if (argc != 1) + return JS_ThrowTypeError( + ctx, "Passed %d arguments, but expected 1", argc); + + js::core::Context& jsctx = *(js::core::Context*)JS_GetContextOpaque(ctx); + + auto str = jsctx.json_stringify(jsctx.wrap(argv[0])); + JS_CHECK_EXC(str); + + pal::snp::TcbVersion tcb_version = nlohmann::json::parse(jsctx.to_str(str)); + + auto buf = jsctx.new_array_buffer_copy((uint8_t*)&tcb_version, sizeof(pal::snp::TcbVersion)); + JS_CHECK_EXC(buf); + + return buf.take(); + } } void ConvertersExtension::install(js::core::Context& ctx) @@ -217,5 +239,7 @@ namespace ccf::js::extensions ctx.new_c_function(js_enable_metrics_logging, "enableMetricsLogging", 1)); ccf.set("pemToId", ctx.new_c_function(js_pem_to_id, "pemToId", 1)); + + ccf.set("jsonToSnpTcbVersion", ctx.new_c_function(js_json_to_tcb_version, "jsonToSnpTcbVersion", 1)); } } diff --git a/tests/e2e_operations.py b/tests/e2e_operations.py index 0605e7247031..21e72856dbd8 100644 --- a/tests/e2e_operations.py +++ b/tests/e2e_operations.py @@ -1039,6 +1039,37 @@ def run_initial_uvm_descriptor_checks(args): return assert False, "No UVM endorsement found in recovery ledger" +def run_initial_snp_tcb_checks(args): + with infram.network.network( + args.nodes, + args.binary_dir, + args.debug_nodes, + args.perf_nodes, + pdb=args.pdb, + ) as network: + LOG.info("Start a network and stop it") + network.start_and_open(args) + primary, _ = network.find_primary() + old_common = infra.network.get_common_folder_name(args.workspace, args.label) + snapshots_dir = network.get_committed_snapshots(primary) + network.stop_all_nodes() + + LOG.info("Check that the a TCB version is present") + ledger_dirs = primary.remote.ledger_paths() + ledger = ccf.ledger.Ledger(ledger_dirs) + first_chunk = next(iter(ledger)) + first_tx = next(first_chunk) + tables = first_tx.get_public_domain().get_tables() + versions = tables["public:ccf.gov.nodes.snp.tcb_versions"] + assert len(versions) == 1, versions + for key in versions.keys(): + amd_cpuids = [ + b"\x00\xA0\x0F\x11", + b"\x00\xA0\x0F\x12", + b"\x00\xA1\x0F\x11", + b"\x00\xA1\x0F\x12"] + assert key in , key + LOG.info(f"Initial TCB version found in ledger: {versions[key]}") def run(args): run_max_uncommitted_tx_count(args) @@ -1055,3 +1086,4 @@ def run(args): run_empty_ledger_dir_check(args) if infra.snp.is_snp(): run_initial_uvm_descriptor_checks(args) + run_initial_snp_tcb_checks(args) From 09a2d13b55d309ff77eb2c3d3d61f1c2a81dfa4a Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Tue, 4 Mar 2025 18:25:19 +0000 Subject: [PATCH 24/29] Add test for tcb_versions table --- tests/code_update.py | 46 +++++++++++++++++++++++++++++++++++++++ tests/e2e_operations.py | 32 --------------------------- tests/infra/consortium.py | 18 +++++++++++++++ 3 files changed, 64 insertions(+), 32 deletions(-) diff --git a/tests/code_update.py b/tests/code_update.py index 806aabf23562..ffdd3f527d8c 100644 --- a/tests/code_update.py +++ b/tests/code_update.py @@ -273,6 +273,51 @@ def get_trusted_host_data(node): return network +@reqs.description("Test tcb version tables") +@reqs.snp_only() +def test_tcb_version_tables(network, args): + primary, _ = network.find_nodes() + LOG.info("Checking that the TCB versions is correctly populated") + cpuid, tcb_version = None, None + with primary.api_versioned_client(api_version=args.gov_api_version) as client: + r = client.get("/gov/service/join-policy") + assert r.status_code == http.HTTPStatus.OK, r + versions = r.body.json()["snp"]["tcbVersions"] + assert len(versions) == 1, f"Expected one TCB version, {versions}" + cpuid, tcb_version = next(iter(versions.items())) + + LOG.info("Removing current cpuid's TCB version") + network.consortium.remove_snp_tcb_version(primary, cpuid) + with primary.api_versioned_client(api_version=args.gov_api_version) as client: + r = client.get("/gov/service/join-policy") + assert r.status_code == http.HTTPStatus.OK, r + versions = r.body.json()["snp"]["tcbVersions"] + assert len(versions) == 0, f"Expected no TCB versions, {versions}" + + LOG.info("Checking new nodes are prevented from joining") + thrown_exception = None + try: + new_node = network.create_node("local://localhost") + network.join_node(new_node, args.package, args, timeout=3) + network.trust_node(new_node, args) + except e: + exception = e + assert (exception is None), f"New node should not have been able to join" + + LOG.info("Adding new cpuid's TCB version") + network.consortium.add_snp_tcb_version(primary, cpuid, tcb_version) + with primary.api_versioned_client(api_version=args.gov_api_version) as client: + r = client.get("/gov/service/join-policy") + assert r.status_code == http.HTTPStatus.OK, r + versions = r.body.json()["snp"]["tcbVersions"] + assert len(versions) == 1, f"Expected one TCB version, {versions}" + + LOG.info("Checking new nodes are allowed to join") + new_node = network.create_node("local://localhost") + network.join_node(new_node, args.package, args, timeout=3) + network.trust_node(new_node, args) + + @reqs.description("Join node with no security policy") @reqs.snp_only() def test_add_node_without_security_policy(network, args): @@ -739,6 +784,7 @@ def run(args): test_add_node_with_stubbed_security_policy(network, args) test_start_node_with_mismatched_host_data(network, args) test_add_node_without_security_policy(network, args) + test_tcb_version_tables(network, args) # Endorsements test_endorsements_tables(network, args) diff --git a/tests/e2e_operations.py b/tests/e2e_operations.py index 21e72856dbd8..d41b2b0053c6 100644 --- a/tests/e2e_operations.py +++ b/tests/e2e_operations.py @@ -1039,38 +1039,6 @@ def run_initial_uvm_descriptor_checks(args): return assert False, "No UVM endorsement found in recovery ledger" -def run_initial_snp_tcb_checks(args): - with infram.network.network( - args.nodes, - args.binary_dir, - args.debug_nodes, - args.perf_nodes, - pdb=args.pdb, - ) as network: - LOG.info("Start a network and stop it") - network.start_and_open(args) - primary, _ = network.find_primary() - old_common = infra.network.get_common_folder_name(args.workspace, args.label) - snapshots_dir = network.get_committed_snapshots(primary) - network.stop_all_nodes() - - LOG.info("Check that the a TCB version is present") - ledger_dirs = primary.remote.ledger_paths() - ledger = ccf.ledger.Ledger(ledger_dirs) - first_chunk = next(iter(ledger)) - first_tx = next(first_chunk) - tables = first_tx.get_public_domain().get_tables() - versions = tables["public:ccf.gov.nodes.snp.tcb_versions"] - assert len(versions) == 1, versions - for key in versions.keys(): - amd_cpuids = [ - b"\x00\xA0\x0F\x11", - b"\x00\xA0\x0F\x12", - b"\x00\xA1\x0F\x11", - b"\x00\xA1\x0F\x12"] - assert key in , key - LOG.info(f"Initial TCB version found in ledger: {versions[key]}") - def run(args): run_max_uncommitted_tx_count(args) run_file_operations(args) diff --git a/tests/infra/consortium.py b/tests/infra/consortium.py index 19c4c2dccda8..4a02dd8f6d60 100644 --- a/tests/infra/consortium.py +++ b/tests/infra/consortium.py @@ -889,6 +889,15 @@ def add_snp_host_data( ) proposal = self.get_any_active_member().propose(remote_node, proposal_body) return self.vote_using_majority(remote_node, proposal, careful_vote) + + def add_snp_tcb_version(self, remote_node, cpuid, new_tcb_version): + proposal_body, careful_vote = self.make_proposal( + "add_snp_tcb_version", + cpuid=cpuid, + tcb_version=new_tcb_version, + ) + proposal = self.get_any_active_member().propose(remote_node, proposal_body) + return self.vote_using_majority(remote_node, proposal, careful_vote) def remove_host_data(self, remote_node, platform, host_data_key): if platform == "virtual": @@ -913,6 +922,15 @@ def remove_snp_host_data(self, remote_node, host_data): ) proposal = self.get_any_active_member().propose(remote_node, proposal_body) return self.vote_using_majority(remote_node, proposal, careful_vote) + + def remove_snp_tcb_version(self, remote_node, cpuid): + proposal_body, careful_vote = self.make_proposal( + "remove_snp_tcb_version", + cpuid=cpuid, + ) + proposal = self.get_any_active_member().propose(remote_node, proposal_body) + return self.vote_using_majority(remote_node, proposal, careful_vote) + def set_node_data(self, remote_node, node_service_id, node_data): proposal, careful_vote = self.make_proposal( From c3c14f9ad9f849a37f54b64daf21f4676fa5dbec Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Tue, 4 Mar 2025 18:37:38 +0000 Subject: [PATCH 25/29] fmt --- samples/constitutions/default/actions.js | 24 +++++--- src/js/extensions/ccf/converters.cpp | 14 +++-- tests/code_update.py | 74 ++++++++++++------------ tests/e2e_operations.py | 2 +- tests/infra/consortium.py | 5 +- 5 files changed, 65 insertions(+), 54 deletions(-) diff --git a/samples/constitutions/default/actions.js b/samples/constitutions/default/actions.js index e5ff321b6a33..46086c750b98 100644 --- a/samples/constitutions/default/actions.js +++ b/samples/constitutions/default/actions.js @@ -1099,15 +1099,23 @@ const actions = new Map([ [ "add_snp_tcb_version", new Action( - function(args) { + function (args) { checkType(args.cpuid, "string", "cpuid"); checkLength(hexStrToBuf(args.cpuid), 4, 4, "cpuid"); checkType(args.tcb_version, "object", "tcb_version"); - checkType(args.tcb_version?.boot_loader, "number", "tcb_version.boot_loader"); + checkType( + args.tcb_version?.boot_loader, + "number", + "tcb_version.boot_loader", + ); checkType(args.tcb_version?.tee, "number", "tcb_version.tee"); checkType(args.tcb_version?.snp, "number", "tcb_version.snp"); - checkType(args.tcb_version?.microcode, "number", "tcb_version.microcode"); + checkType( + args.tcb_version?.microcode, + "number", + "tcb_version.microcode", + ); }, function (args) { ccf.kv["public:ccf.gov.nodes.snp.tcb_versions"].set( @@ -1117,8 +1125,8 @@ const actions = new Map([ // Is this required? //invalidateOtherOpenProposals(proposalId); - } - ) + }, + ), ], [ "remove_snp_host_data", @@ -1178,15 +1186,15 @@ const actions = new Map([ [ "remove_snp_tcb_version", new Action( - function(args) { + function (args) { checkType(args.cpuid, "string", "cpuid"); checkLength(hexStrToBuf(args.cpuid), 4, 4, "cpuid"); }, function (args) { const cpuid = hexStrToBuf(args.cpuid); ccf.kv["public:ccf.gov.nodes.snp.tcb_versions"].delete(cpuid); - } - ) + }, + ), ], [ "set_node_data", diff --git a/src/js/extensions/ccf/converters.cpp b/src/js/extensions/ccf/converters.cpp index 5a8784be1f52..a56256f89d00 100644 --- a/src/js/extensions/ccf/converters.cpp +++ b/src/js/extensions/ccf/converters.cpp @@ -7,12 +7,12 @@ #include "ccf/js/extensions/ccf/converters.h" #include "ccf/js/core/context.h" +#include "ccf/pal/attestation_sev_snp.h" #include "ccf/version.h" #include "js/checks.h" #include "node/rpc/jwt_management.h" -#include "ccf/pal/attestation_sev_snp.h" -#include +#include #include namespace ccf::js::extensions @@ -206,9 +206,11 @@ namespace ccf::js::extensions auto str = jsctx.json_stringify(jsctx.wrap(argv[0])); JS_CHECK_EXC(str); - pal::snp::TcbVersion tcb_version = nlohmann::json::parse(jsctx.to_str(str)); + pal::snp::TcbVersion tcb_version = + nlohmann::json::parse(jsctx.to_str(str)); - auto buf = jsctx.new_array_buffer_copy((uint8_t*)&tcb_version, sizeof(pal::snp::TcbVersion)); + auto buf = jsctx.new_array_buffer_copy( + (uint8_t*)&tcb_version, sizeof(pal::snp::TcbVersion)); JS_CHECK_EXC(buf); return buf.take(); @@ -240,6 +242,8 @@ namespace ccf::js::extensions ccf.set("pemToId", ctx.new_c_function(js_pem_to_id, "pemToId", 1)); - ccf.set("jsonToSnpTcbVersion", ctx.new_c_function(js_json_to_tcb_version, "jsonToSnpTcbVersion", 1)); + ccf.set( + "jsonToSnpTcbVersion", + ctx.new_c_function(js_json_to_tcb_version, "jsonToSnpTcbVersion", 1)); } } diff --git a/tests/code_update.py b/tests/code_update.py index ffdd3f527d8c..864c2034486c 100644 --- a/tests/code_update.py +++ b/tests/code_update.py @@ -276,46 +276,46 @@ def get_trusted_host_data(node): @reqs.description("Test tcb version tables") @reqs.snp_only() def test_tcb_version_tables(network, args): - primary, _ = network.find_nodes() - LOG.info("Checking that the TCB versions is correctly populated") - cpuid, tcb_version = None, None - with primary.api_versioned_client(api_version=args.gov_api_version) as client: - r = client.get("/gov/service/join-policy") - assert r.status_code == http.HTTPStatus.OK, r - versions = r.body.json()["snp"]["tcbVersions"] - assert len(versions) == 1, f"Expected one TCB version, {versions}" - cpuid, tcb_version = next(iter(versions.items())) - - LOG.info("Removing current cpuid's TCB version") - network.consortium.remove_snp_tcb_version(primary, cpuid) - with primary.api_versioned_client(api_version=args.gov_api_version) as client: - r = client.get("/gov/service/join-policy") - assert r.status_code == http.HTTPStatus.OK, r - versions = r.body.json()["snp"]["tcbVersions"] - assert len(versions) == 0, f"Expected no TCB versions, {versions}" - - LOG.info("Checking new nodes are prevented from joining") - thrown_exception = None - try: + primary, _ = network.find_nodes() + LOG.info("Checking that the TCB versions is correctly populated") + cpuid, tcb_version = None, None + with primary.api_versioned_client(api_version=args.gov_api_version) as client: + r = client.get("/gov/service/join-policy") + assert r.status_code == http.HTTPStatus.OK, r + versions = r.body.json()["snp"]["tcbVersions"] + assert len(versions) == 1, f"Expected one TCB version, {versions}" + cpuid, tcb_version = next(iter(versions.items())) + + LOG.info("Removing current cpuid's TCB version") + network.consortium.remove_snp_tcb_version(primary, cpuid) + with primary.api_versioned_client(api_version=args.gov_api_version) as client: + r = client.get("/gov/service/join-policy") + assert r.status_code == http.HTTPStatus.OK, r + versions = r.body.json()["snp"]["tcbVersions"] + assert len(versions) == 0, f"Expected no TCB versions, {versions}" + + LOG.info("Checking new nodes are prevented from joining") + thrown_exception = None + try: + new_node = network.create_node("local://localhost") + network.join_node(new_node, args.package, args, timeout=3) + network.trust_node(new_node, args) + except Exception as e: + thrown_exception = e + assert thrown_exception is None, "New node should not have been able to join" + + LOG.info("Adding new cpuid's TCB version") + network.consortium.add_snp_tcb_version(primary, cpuid, tcb_version) + with primary.api_versioned_client(api_version=args.gov_api_version) as client: + r = client.get("/gov/service/join-policy") + assert r.status_code == http.HTTPStatus.OK, r + versions = r.body.json()["snp"]["tcbVersions"] + assert len(versions) == 1, f"Expected one TCB version, {versions}" + + LOG.info("Checking new nodes are allowed to join") new_node = network.create_node("local://localhost") network.join_node(new_node, args.package, args, timeout=3) network.trust_node(new_node, args) - except e: - exception = e - assert (exception is None), f"New node should not have been able to join" - - LOG.info("Adding new cpuid's TCB version") - network.consortium.add_snp_tcb_version(primary, cpuid, tcb_version) - with primary.api_versioned_client(api_version=args.gov_api_version) as client: - r = client.get("/gov/service/join-policy") - assert r.status_code == http.HTTPStatus.OK, r - versions = r.body.json()["snp"]["tcbVersions"] - assert len(versions) == 1, f"Expected one TCB version, {versions}" - - LOG.info("Checking new nodes are allowed to join") - new_node = network.create_node("local://localhost") - network.join_node(new_node, args.package, args, timeout=3) - network.trust_node(new_node, args) @reqs.description("Join node with no security policy") diff --git a/tests/e2e_operations.py b/tests/e2e_operations.py index d41b2b0053c6..0605e7247031 100644 --- a/tests/e2e_operations.py +++ b/tests/e2e_operations.py @@ -1039,6 +1039,7 @@ def run_initial_uvm_descriptor_checks(args): return assert False, "No UVM endorsement found in recovery ledger" + def run(args): run_max_uncommitted_tx_count(args) run_file_operations(args) @@ -1054,4 +1055,3 @@ def run(args): run_empty_ledger_dir_check(args) if infra.snp.is_snp(): run_initial_uvm_descriptor_checks(args) - run_initial_snp_tcb_checks(args) diff --git a/tests/infra/consortium.py b/tests/infra/consortium.py index 4a02dd8f6d60..53040559affd 100644 --- a/tests/infra/consortium.py +++ b/tests/infra/consortium.py @@ -889,7 +889,7 @@ def add_snp_host_data( ) proposal = self.get_any_active_member().propose(remote_node, proposal_body) return self.vote_using_majority(remote_node, proposal, careful_vote) - + def add_snp_tcb_version(self, remote_node, cpuid, new_tcb_version): proposal_body, careful_vote = self.make_proposal( "add_snp_tcb_version", @@ -922,7 +922,7 @@ def remove_snp_host_data(self, remote_node, host_data): ) proposal = self.get_any_active_member().propose(remote_node, proposal_body) return self.vote_using_majority(remote_node, proposal, careful_vote) - + def remove_snp_tcb_version(self, remote_node, cpuid): proposal_body, careful_vote = self.make_proposal( "remove_snp_tcb_version", @@ -931,7 +931,6 @@ def remove_snp_tcb_version(self, remote_node, cpuid): proposal = self.get_any_active_member().propose(remote_node, proposal_body) return self.vote_using_majority(remote_node, proposal, careful_vote) - def set_node_data(self, remote_node, node_service_id, node_data): proposal, careful_vote = self.make_proposal( "set_node_data", From f6a03bb23ae90130889e14cf87efc0430f2f7f19 Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Wed, 5 Mar 2025 11:38:22 +0000 Subject: [PATCH 26/29] fixup --- src/js/extensions/ccf/converters.cpp | 2 +- src/service/internal_tables_access.h | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/js/extensions/ccf/converters.cpp b/src/js/extensions/ccf/converters.cpp index a56256f89d00..2ab92e114f8b 100644 --- a/src/js/extensions/ccf/converters.cpp +++ b/src/js/extensions/ccf/converters.cpp @@ -207,7 +207,7 @@ namespace ccf::js::extensions JS_CHECK_EXC(str); pal::snp::TcbVersion tcb_version = - nlohmann::json::parse(jsctx.to_str(str)); + nlohmann::json::parse(jsctx.to_str(str).value()); auto buf = jsctx.new_array_buffer_copy( (uint8_t*)&tcb_version, sizeof(pal::snp::TcbVersion)); diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index 5c9dd6884be8..aa4f2e7acfad 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -905,13 +905,13 @@ namespace ccf return; } 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() != attestation.cpuid_fam_id || + cpuid.get_model_id() != attestation.cpuid_mod_id || + cpuid.stepping != attestation.cpuid_step) { LOG_FAIL_FMT( "Snp cpuid does not match attestation cpuid ({} != {}, {}, {})", - cpuid->hex_str(), + cpuid.hex_str(), attestation.cpuid_fam_id, attestation.cpuid_mod_id, attestation.cpuid_step); From df38f540b2a4ccf860b24b0d34145c81201c570b Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Wed, 5 Mar 2025 15:44:18 +0000 Subject: [PATCH 27/29] Fix formatting of cpuid --- .vscode/c_cpp_properties.json | 3 ++- include/ccf/pal/attestation_sev_snp.h | 23 ++++++++++++++--------- samples/constitutions/default/actions.js | 4 +++- src/node/rpc/node_frontend.h | 2 -- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 9b42828f8c83..ac548410cabd 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -2,7 +2,8 @@ "configurations": [ { "name": "Linux", - "compileCommands": "${workspaceFolder}/build/compile_commands.json" + "compileCommands": "${workspaceFolder}/build/compile_commands.json", + "includePath": ["${workspaceFolder}/include", "${workspaceFolder}/src"] } ], "version": 4 diff --git a/include/ccf/pal/attestation_sev_snp.h b/include/ccf/pal/attestation_sev_snp.h index d374fdd19ffe..74d747a1fb40 100644 --- a/include/ccf/pal/attestation_sev_snp.h +++ b/include/ccf/pal/attestation_sev_snp.h @@ -282,8 +282,10 @@ QPHfbkH0CyPfhl1jWhJFZasCAwEAAQ== bool operator==(const CPUID&) const = default; std::string hex_str() const { - auto begin = reinterpret_cast(this); - return ccf::ds::to_hex(begin, begin + sizeof(CPUID)); + uint8_t buf[sizeof(CPUID)]; + memcpy(buf, this, sizeof(CPUID)); + std::reverse(buf, buf + sizeof(CPUID)); // fix little endianness of AMD + return ccf::ds::to_hex(buf, buf + sizeof(CPUID)); } inline uint8_t get_family_id() const { @@ -300,6 +302,15 @@ QPHfbkH0CyPfhl1jWhJFZasCAwEAAQ== CPUID, stepping, base_model, base_family, extended_model, extended_family); static_assert( sizeof(CPUID) == sizeof(uint32_t), "Can't cast CPUID to uint32_t"); + static CPUID cpuid_from_hex(const std::string& hex_str) + { + CPUID ret; + auto buf_ptr = reinterpret_cast(&ret); + ccf::ds::from_hex( + hex_str, buf_ptr, buf_ptr + sizeof(CPUID)); + std::reverse(buf_ptr, buf_ptr + sizeof(CPUID)); //fix little endianness of AMD + return ret; + } union UnionedCPUID { @@ -331,13 +342,7 @@ namespace ccf::kv::serialisers static ccf::pal::snp::CPUID from_serialised(const SerialisedEntry& data) { - ccf::pal::snp::CPUID ret; - auto buf_ptr = reinterpret_cast(&ret); - ccf::ds::from_hex( - std::string(data.data(), data.end()), - buf_ptr, - buf_ptr + sizeof(ccf::pal::snp::CPUID)); - return ret; + return ccf::pal::snp::cpuid_from_hex(std::string(data.data(), data.end())); } }; } \ No newline at end of file diff --git a/samples/constitutions/default/actions.js b/samples/constitutions/default/actions.js index 46086c750b98..5531b32fe246 100644 --- a/samples/constitutions/default/actions.js +++ b/samples/constitutions/default/actions.js @@ -1192,7 +1192,9 @@ const actions = new Map([ }, function (args) { const cpuid = hexStrToBuf(args.cpuid); - ccf.kv["public:ccf.gov.nodes.snp.tcb_versions"].delete(cpuid); + if ( ccf.kv["public:ccf.gov.nodes.snp.tcb_versions"].has(cpuid)) { + ccf.kv["public:ccf.gov.nodes.snp.tcb_versions"].delete(cpuid); + } }, ), ], diff --git a/src/node/rpc/node_frontend.h b/src/node/rpc/node_frontend.h index 670f2d3f86f8..188ef7ef59cb 100644 --- a/src/node/rpc/node_frontend.h +++ b/src/node/rpc/node_frontend.h @@ -1621,8 +1621,6 @@ namespace ccf ctx.tx, in.measurement, in.quote_info.format); } - InternalTablesAccess::trust_static_snp_tcb_version(ctx.tx); - switch (in.quote_info.format) { case QuoteFormat::insecure_virtual: From f1cc568feb4aef047e08a92733414c9cb4cb51ed Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Thu, 6 Mar 2025 17:57:34 +0000 Subject: [PATCH 28/29] TMP store hex string of cpuid --- include/ccf/pal/attestation_sev_snp.h | 30 +++---- include/ccf/service/tables/tcb_verification.h | 2 +- samples/constitutions/default/actions.js | 7 +- src/node/gov/handlers/service_state.h | 5 +- src/node/quote.cpp | 3 +- src/service/internal_tables_access.h | 10 +-- tests/code_update.py | 80 +++++++++++-------- 7 files changed, 75 insertions(+), 62 deletions(-) diff --git a/include/ccf/pal/attestation_sev_snp.h b/include/ccf/pal/attestation_sev_snp.h index 74d747a1fb40..411623de98cd 100644 --- a/include/ccf/pal/attestation_sev_snp.h +++ b/include/ccf/pal/attestation_sev_snp.h @@ -282,10 +282,10 @@ QPHfbkH0CyPfhl1jWhJFZasCAwEAAQ== bool operator==(const CPUID&) const = default; std::string hex_str() const { - uint8_t buf[sizeof(CPUID)]; - memcpy(buf, this, sizeof(CPUID)); - std::reverse(buf, buf + sizeof(CPUID)); // fix little endianness of AMD - return ccf::ds::to_hex(buf, buf + sizeof(CPUID)); + CPUID buf = *this; + auto buf_ptr = reinterpret_cast(&buf); + std::reverse(buf_ptr, buf_ptr + sizeof(CPUID)); + return ccf::ds::to_hex(buf_ptr, buf_ptr + sizeof(CPUID)); } inline uint8_t get_family_id() const { @@ -312,18 +312,20 @@ QPHfbkH0CyPfhl1jWhJFZasCAwEAAQ== return ret; } - union UnionedCPUID - { - uint32_t eax; - CPUID cpuid; - }; - static CPUID get_cpuid() { - UnionedCPUID cpuid_eax; - cpuid_eax.eax = 0; - asm volatile("cpuid" : "=a"(cpuid_eax.eax) : "a"(1)); - return cpuid_eax.cpuid; + uint32_t ieax = 1; + uint64_t iebx = 0; + uint64_t iecx = 0; + uint64_t iedx = 0; + uint32_t oeax = 0; + uint64_t oebx = 0; + uint64_t oecx = 0; + uint64_t oedx = 0; + // pass in e{b,c,d}x to prevent cpuid from blatting other registers + asm volatile("cpuid" : "=a"(oeax), "=b"(oebx), "=c"(oecx), "=d"(oedx): "a"(ieax), "b"(iebx), "c"(iecx), "d"(iedx)); + auto cpuid = *reinterpret_cast(&oeax); + return cpuid; } } diff --git a/include/ccf/service/tables/tcb_verification.h b/include/ccf/service/tables/tcb_verification.h index f28861677355..2e01f53ff1e6 100644 --- a/include/ccf/service/tables/tcb_verification.h +++ b/include/ccf/service/tables/tcb_verification.h @@ -8,7 +8,7 @@ namespace ccf { - using SnpTcbVersionMap = ServiceMap; + using SnpTcbVersionMap = ServiceMap; namespace Tables { diff --git a/samples/constitutions/default/actions.js b/samples/constitutions/default/actions.js index 5531b32fe246..c1b862068a6e 100644 --- a/samples/constitutions/default/actions.js +++ b/samples/constitutions/default/actions.js @@ -1117,14 +1117,13 @@ const actions = new Map([ "tcb_version.microcode", ); }, - function (args) { + function (args, proposalId) { ccf.kv["public:ccf.gov.nodes.snp.tcb_versions"].set( - hexStrToBuf(args.cpuid), + ccf.strToBuf(args.cpuid), ccf.jsonToSnpTcbVersion(args.tcb_version), ); - // Is this required? - //invalidateOtherOpenProposals(proposalId); + invalidateOtherOpenProposals(proposalId); }, ), ], diff --git a/src/node/gov/handlers/service_state.h b/src/node/gov/handlers/service_state.h index c3f5c4909ac9..f03b816fb196 100644 --- a/src/node/gov/handlers/service_state.h +++ b/src/node/gov/handlers/service_state.h @@ -619,11 +619,12 @@ namespace ccf::gov::endpoints auto tcb_versions_handle = ctx.tx.template ro( ccf::Tables::SNP_TCB_VERSIONS); + tcb_versions_handle->foreach( [&snp_tcb_versions]( - const pal::snp::CPUID& cpuid, + const std::string& cpuid, const pal::snp::TcbVersion& tcb_version) { - snp_tcb_versions[cpuid.hex_str()] = tcb_version; + snp_tcb_versions[cpuid] = tcb_version; return true; }); snp_policy["tcbVersions"] = snp_tcb_versions; diff --git a/src/node/quote.cpp b/src/node/quote.cpp index 9383663710fb..65d09d1dfd51 100644 --- a/src/node/quote.cpp +++ b/src/node/quote.cpp @@ -280,7 +280,8 @@ namespace ccf auto h = tx.ro(Tables::SNP_TCB_VERSIONS); // expensive but there should not be many entries h->foreach([&min_tcb_opt, &attestation]( - const pal::snp::CPUID& cpuid, const pal::snp::TcbVersion& v) { + const std::string cpuid_hex, const pal::snp::TcbVersion& 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 && diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index aa4f2e7acfad..769942d1729d 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -841,7 +841,7 @@ namespace ccf .reserved = {0}, .snp = 0x18, .microcode = 0xDB}; - h->put(milan_chip_id, milan_tcb_version); + h->put(milan_chip_id.hex_str(), milan_tcb_version); constexpr pal::snp::CPUID milan_x_chip_id{ .stepping = 0x2, @@ -857,7 +857,7 @@ namespace ccf .reserved = {0}, .snp = 0x18, .microcode = 0x44}; - h->put(milan_x_chip_id, milan_x_tcb_version); + h->put(milan_x_chip_id.hex_str(), milan_x_tcb_version); constexpr pal::snp::CPUID genoa_chip_id{ .stepping = 0x1, @@ -873,7 +873,7 @@ namespace ccf .reserved = {0}, .snp = 0x17, .microcode = 0x54}; - h->put(genoa_chip_id, genoa_tcb_version); + h->put(genoa_chip_id.hex_str(), genoa_tcb_version); constexpr pal::snp::CPUID genoa_x_chip_id{ .stepping = 0x2, @@ -889,7 +889,7 @@ namespace ccf .reserved = {0}, .snp = 0x17, .microcode = 0x4F}; - h->put(genoa_x_chip_id, genoa_x_tcb_version); + h->put(genoa_x_chip_id.hex_str(), genoa_x_tcb_version); } static void trust_node_snp_tcb_version( @@ -919,7 +919,7 @@ namespace ccf return; } auto h = tx.wo(Tables::SNP_TCB_VERSIONS); - h->put(cpuid, attestation.reported_tcb); + h->put(cpuid.hex_str(), attestation.reported_tcb); } static void init_configuration( diff --git a/tests/code_update.py b/tests/code_update.py index 864c2034486c..45e5d97fddb2 100644 --- a/tests/code_update.py +++ b/tests/code_update.py @@ -286,6 +286,16 @@ def test_tcb_version_tables(network, args): assert len(versions) == 1, f"Expected one TCB version, {versions}" cpuid, tcb_version = next(iter(versions.items())) + LOG.info("Change current cpuid's TCB version") + test_tcb_version = {"boot_loader": 0, "microcode": 0, "snp": 0, "tee": 0} + network.consortium.add_snp_tcb_version(primary, cpuid, test_tcb_version) + with primary.api_versioned_client(api_version=args.gov_api_version) as client: + r = client.get("/gov/service/join-policy") + assert r.status_code == http.HTTPStatus.OK, r + versions = r.body.json()["snp"]["tcbVersions"] + assert cpuid in versions, f"Expected {cpuid} in TCB versions, {versions}" + assert versions[cpuid] == test_tcb_version, f"TCB version does not match, {versions}" + LOG.info("Removing current cpuid's TCB version") network.consortium.remove_snp_tcb_version(primary, cpuid) with primary.api_versioned_client(api_version=args.gov_api_version) as client: @@ -768,43 +778,43 @@ def run(args): ) as network: network.start_and_open(args) - test_verify_quotes(network, args) - - # Measurements - test_measurements_tables(network, args) - if not snp.IS_SNP: - test_add_node_with_untrusted_measurement(network, args) - - # Host data/security policy - test_host_data_tables(network, args) - test_add_node_with_untrusted_host_data(network, args) - +# test_verify_quotes(network, args) +# +# # Measurements +# test_measurements_tables(network, args) +# if not snp.IS_SNP: +# test_add_node_with_untrusted_measurement(network, args) +# +# # Host data/security policy +# test_host_data_tables(network, args) +# test_add_node_with_untrusted_host_data(network, args) +# if snp.IS_SNP: - # Virtual has no security policy, _only_ host data (unassociated with anything) - test_add_node_with_stubbed_security_policy(network, args) - test_start_node_with_mismatched_host_data(network, args) - test_add_node_without_security_policy(network, args) +# # Virtual has no security policy, _only_ host data (unassociated with anything) +# test_add_node_with_stubbed_security_policy(network, args) +# test_start_node_with_mismatched_host_data(network, args) +# test_add_node_without_security_policy(network, args) test_tcb_version_tables(network, args) - - # Endorsements - test_endorsements_tables(network, args) - test_add_node_with_no_uvm_endorsements(network, args) - - if not snp.IS_SNP: - # NB: Assumes the current nodes are still using args.package, so must run before test_update_all_nodes - test_proposal_invalidation(network, args) - - # This is in practice equivalent to either "unknown measurement" or "unknown host data", but is explicitly - # testing that (without artifically removing/corrupting those values) a replacement package differs - # in one of these values - test_add_node_with_different_package(network, args) - test_update_all_nodes(network, args) - - # Run again at the end to confirm current nodes are acceptable - test_verify_quotes(network, args) - - if snp.IS_SNP: - test_add_node_with_no_uvm_endorsements_in_kv(network, args) +# +# # Endorsements +# test_endorsements_tables(network, args) +# test_add_node_with_no_uvm_endorsements(network, args) +# +# if not snp.IS_SNP: +# # NB: Assumes the current nodes are still using args.package, so must run before test_update_all_nodes +# test_proposal_invalidation(network, args) +# +# # This is in practice equivalent to either "unknown measurement" or "unknown host data", but is explicitly +# # testing that (without artifically removing/corrupting those values) a replacement package differs +# # in one of these values +# test_add_node_with_different_package(network, args) +# test_update_all_nodes(network, args) +# +# # Run again at the end to confirm current nodes are acceptable +# test_verify_quotes(network, args) +# +# if snp.IS_SNP: +# test_add_node_with_no_uvm_endorsements_in_kv(network, args) if __name__ == "__main__": From 15b50cbb86a359c0fc6927c842113476521c36bc Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Fri, 7 Mar 2025 13:40:15 +0000 Subject: [PATCH 29/29] Fix converters --- include/ccf/js/extensions/ccf/converters.h | 1 - samples/constitutions/default/actions.js | 10 +-- src/js/extensions/ccf/converters.cpp | 25 -------- tests/code_update.py | 74 +++++++++++----------- 4 files changed, 43 insertions(+), 67 deletions(-) diff --git a/include/ccf/js/extensions/ccf/converters.h b/include/ccf/js/extensions/ccf/converters.h index df58b3b4ac36..8af18051fe52 100644 --- a/include/ccf/js/extensions/ccf/converters.h +++ b/include/ccf/js/extensions/ccf/converters.h @@ -15,7 +15,6 @@ namespace ccf::js::extensions * - ccf.bufToJsonCompatible * * - ccf.pemToId - * - ccf.jsonToTcbVersion * * - ccf.enableUntrustedDateTime * - ccf.enableMetricsLogging diff --git a/samples/constitutions/default/actions.js b/samples/constitutions/default/actions.js index c1b862068a6e..b3c230945949 100644 --- a/samples/constitutions/default/actions.js +++ b/samples/constitutions/default/actions.js @@ -1101,7 +1101,7 @@ const actions = new Map([ new Action( function (args) { checkType(args.cpuid, "string", "cpuid"); - checkLength(hexStrToBuf(args.cpuid), 4, 4, "cpuid"); + checkLength(ccf.strToBuf(args.cpuid), 8, 8, "cpuid"); checkType(args.tcb_version, "object", "tcb_version"); checkType( @@ -1120,7 +1120,7 @@ const actions = new Map([ function (args, proposalId) { ccf.kv["public:ccf.gov.nodes.snp.tcb_versions"].set( ccf.strToBuf(args.cpuid), - ccf.jsonToSnpTcbVersion(args.tcb_version), + ccf.jsonCompatibleToBuf(args.tcb_version), ); invalidateOtherOpenProposals(proposalId); @@ -1187,12 +1187,14 @@ const actions = new Map([ new Action( function (args) { checkType(args.cpuid, "string", "cpuid"); - checkLength(hexStrToBuf(args.cpuid), 4, 4, "cpuid"); + checkLength(ccf.strToBuf(args.cpuid), 8, 8, "cpuid"); }, function (args) { - const cpuid = hexStrToBuf(args.cpuid); + const cpuid = ccf.strToBuf(args.cpuid); if ( ccf.kv["public:ccf.gov.nodes.snp.tcb_versions"].has(cpuid)) { ccf.kv["public:ccf.gov.nodes.snp.tcb_versions"].delete(cpuid); + } else { + throw new Error("CPUID not found"); } }, ), diff --git a/src/js/extensions/ccf/converters.cpp b/src/js/extensions/ccf/converters.cpp index 2ab92e114f8b..a9127a3e083a 100644 --- a/src/js/extensions/ccf/converters.cpp +++ b/src/js/extensions/ccf/converters.cpp @@ -193,28 +193,6 @@ namespace ccf::js::extensions ctx, "Failed to parse PEM: %s", exc.what()); } } - - JSValue js_json_to_tcb_version( - JSContext* ctx, JSValueConst, int argc, JSValueConst* argv) - { - if (argc != 1) - return JS_ThrowTypeError( - ctx, "Passed %d arguments, but expected 1", argc); - - js::core::Context& jsctx = *(js::core::Context*)JS_GetContextOpaque(ctx); - - auto str = jsctx.json_stringify(jsctx.wrap(argv[0])); - JS_CHECK_EXC(str); - - pal::snp::TcbVersion tcb_version = - nlohmann::json::parse(jsctx.to_str(str).value()); - - auto buf = jsctx.new_array_buffer_copy( - (uint8_t*)&tcb_version, sizeof(pal::snp::TcbVersion)); - JS_CHECK_EXC(buf); - - return buf.take(); - } } void ConvertersExtension::install(js::core::Context& ctx) @@ -242,8 +220,5 @@ namespace ccf::js::extensions ccf.set("pemToId", ctx.new_c_function(js_pem_to_id, "pemToId", 1)); - ccf.set( - "jsonToSnpTcbVersion", - ctx.new_c_function(js_json_to_tcb_version, "jsonToSnpTcbVersion", 1)); } } diff --git a/tests/code_update.py b/tests/code_update.py index 45e5d97fddb2..f79b545713ec 100644 --- a/tests/code_update.py +++ b/tests/code_update.py @@ -310,9 +310,9 @@ def test_tcb_version_tables(network, args): new_node = network.create_node("local://localhost") network.join_node(new_node, args.package, args, timeout=3) network.trust_node(new_node, args) - except Exception as e: + except TimeoutError as e: thrown_exception = e - assert thrown_exception is None, "New node should not have been able to join" + assert thrown_exception is not None, "New node should not have been able to join" LOG.info("Adding new cpuid's TCB version") network.consortium.add_snp_tcb_version(primary, cpuid, tcb_version) @@ -778,43 +778,43 @@ def run(args): ) as network: network.start_and_open(args) -# test_verify_quotes(network, args) -# -# # Measurements -# test_measurements_tables(network, args) -# if not snp.IS_SNP: -# test_add_node_with_untrusted_measurement(network, args) -# -# # Host data/security policy -# test_host_data_tables(network, args) -# test_add_node_with_untrusted_host_data(network, args) -# + test_verify_quotes(network, args) + + # Measurements + test_measurements_tables(network, args) + if not snp.IS_SNP: + test_add_node_with_untrusted_measurement(network, args) + + # Host data/security policy + test_host_data_tables(network, args) + test_add_node_with_untrusted_host_data(network, args) + if snp.IS_SNP: -# # Virtual has no security policy, _only_ host data (unassociated with anything) -# test_add_node_with_stubbed_security_policy(network, args) -# test_start_node_with_mismatched_host_data(network, args) -# test_add_node_without_security_policy(network, args) + # Virtual has no security policy, _only_ host data (unassociated with anything) + test_add_node_with_stubbed_security_policy(network, args) + test_start_node_with_mismatched_host_data(network, args) + test_add_node_without_security_policy(network, args) test_tcb_version_tables(network, args) -# -# # Endorsements -# test_endorsements_tables(network, args) -# test_add_node_with_no_uvm_endorsements(network, args) -# -# if not snp.IS_SNP: -# # NB: Assumes the current nodes are still using args.package, so must run before test_update_all_nodes -# test_proposal_invalidation(network, args) -# -# # This is in practice equivalent to either "unknown measurement" or "unknown host data", but is explicitly -# # testing that (without artifically removing/corrupting those values) a replacement package differs -# # in one of these values -# test_add_node_with_different_package(network, args) -# test_update_all_nodes(network, args) -# -# # Run again at the end to confirm current nodes are acceptable -# test_verify_quotes(network, args) -# -# if snp.IS_SNP: -# test_add_node_with_no_uvm_endorsements_in_kv(network, args) + + # Endorsements + test_endorsements_tables(network, args) + test_add_node_with_no_uvm_endorsements(network, args) + + if not snp.IS_SNP: + # NB: Assumes the current nodes are still using args.package, so must run before test_update_all_nodes + test_proposal_invalidation(network, args) + + # This is in practice equivalent to either "unknown measurement" or "unknown host data", but is explicitly + # testing that (without artifically removing/corrupting those values) a replacement package differs + # in one of these values + test_add_node_with_different_package(network, args) + test_update_all_nodes(network, args) + + # Run again at the end to confirm current nodes are acceptable + test_verify_quotes(network, args) + + if snp.IS_SNP: + test_add_node_with_no_uvm_endorsements_in_kv(network, args) if __name__ == "__main__":