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-/--->>>--/ ... 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/node/quote.h b/include/ccf/node/quote.h index f499857d7ead..f5b2d73e55ef 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" @@ -22,6 +23,8 @@ namespace ccf FailedInvalidHostData, FailedInvalidQuotedPublicKey, FailedUVMEndorsementsNotFound, + FailedInvalidCPUID, + FailedInvalidTcbVersion }; class AttestationProvider @@ -35,10 +38,16 @@ 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, 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/attestation_sev_snp.h b/include/ccf/pal/attestation_sev_snp.h index f9c71facfaa5..411623de98cd 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 @@ -141,7 +143,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 */ @@ -261,4 +266,85 @@ QPHfbkH0CyPfhl1jWhJFZasCAwEAAQ== virtual ~AttestationInterface() = default; }; + + static uint8_t MIN_TCB_VERIF_VERSION = 3; +#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; + + bool operator==(const CPUID&) const = default; + std::string hex_str() const + { + 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 + { + return this->base_family + this->extended_family; + } + inline uint8_t get_model_id() const + { + return (this->extended_model << 4) | this->base_model; + } + }; +#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"); + 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; + } + + static CPUID get_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; + } } + +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::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) + { + return ccf::pal::snp::cpuid_from_hex(std::string(data.data(), data.end())); + } + }; +} \ No newline at end of file diff --git a/include/ccf/pal/snp_ioctl5.h b/include/ccf/pal/snp_ioctl5.h index a40251bb5f25..972e273994a1 100644 --- a/include/ccf/pal/snp_ioctl5.h +++ b/include/ccf/pal/snp_ioctl5.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/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/include/ccf/service/tables/tcb_verification.h b/include/ccf/service/tables/tcb_verification.h new file mode 100644 index 000000000000..2e01f53ff1e6 --- /dev/null +++ b/include/ccf/service/tables/tcb_verification.h @@ -0,0 +1,18 @@ +// 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 = ServiceMap; + + namespace Tables + { + static constexpr auto SNP_TCB_VERSIONS = + "public:ccf.gov.nodes.snp.tcb_versions"; + } +} \ No newline at end of file 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/samples/constitutions/default/actions.js b/samples/constitutions/default/actions.js index 0dd2e8ada4c9..b3c230945949 100644 --- a/samples/constitutions/default/actions.js +++ b/samples/constitutions/default/actions.js @@ -1096,6 +1096,37 @@ const actions = new Map([ }, ), ], + [ + "add_snp_tcb_version", + new Action( + function (args) { + checkType(args.cpuid, "string", "cpuid"); + checkLength(ccf.strToBuf(args.cpuid), 8, 8, "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, proposalId) { + ccf.kv["public:ccf.gov.nodes.snp.tcb_versions"].set( + ccf.strToBuf(args.cpuid), + ccf.jsonCompatibleToBuf(args.tcb_version), + ); + + invalidateOtherOpenProposals(proposalId); + }, + ), + ], [ "remove_snp_host_data", new Action( @@ -1151,6 +1182,23 @@ const actions = new Map([ }, ), ], + [ + "remove_snp_tcb_version", + new Action( + function (args) { + checkType(args.cpuid, "string", "cpuid"); + checkLength(ccf.strToBuf(args.cpuid), 8, 8, "cpuid"); + }, + function (args) { + 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"); + } + }, + ), + ], [ "set_node_data", new Action( diff --git a/src/js/extensions/ccf/converters.cpp b/src/js/extensions/ccf/converters.cpp index b53649e9e7bf..a9127a3e083a 100644 --- a/src/js/extensions/ccf/converters.cpp +++ b/src/js/extensions/ccf/converters.cpp @@ -7,10 +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 #include namespace ccf::js::extensions @@ -217,5 +219,6 @@ 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)); + } } 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/src/node/gov/handlers/service_state.h b/src/node/gov/handlers/service_state.h index 7fe0e3f6a636..f03b816fb196 100644 --- a/src/node/gov/handlers/service_state.h +++ b/src/node/gov/handlers/service_state.h @@ -615,6 +615,20 @@ 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 std::string& cpuid, + const pal::snp::TcbVersion& tcb_version) { + snp_tcb_versions[cpuid] = 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 f821bab473a0..65d09d1dfd51 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" @@ -138,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) { @@ -231,6 +255,62 @@ 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 < pal::snp::MIN_TCB_VERIF_VERSION) + { + // Necessary until all C-ACI servers are updated + return QuoteVerificationResult::Verified; + } + + std::optional min_tcb_opt = std::nullopt; + + auto h = tx.ro(Tables::SNP_TCB_VERSIONS); + // expensive but there should not be many entries + h->foreach([&min_tcb_opt, &attestation]( + 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 && + cpuid.stepping == attestation.cpuid_step) + { + min_tcb_opt = v; + return false; + } + return true; + }); + + 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 +343,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/member_frontend.h b/src/node/rpc/member_frontend.h index caddcf3a659a..12acba229b78 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" @@ -508,7 +509,8 @@ 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/node/rpc/node_frontend.h b/src/node/rpc/node_frontend.h index f5e69e1dd0fc..188ef7ef59cb 100644 --- a/src/node/rpc/node_frontend.h +++ b/src/node/rpc/node_frontend.h @@ -1636,6 +1636,8 @@ 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: @@ -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; } diff --git a/src/pal/quote_generation.h b/src/pal/quote_generation.h index 347da0fff7c3..f5a7aa6ce06d 100644 --- a/src/pal/quote_generation.h +++ b/src/pal/quote_generation.h @@ -3,6 +3,7 @@ #pragma once #include "ccf/crypto/hash_provider.h" +#include "ccf/pal/attestation.h" #include "ds/files.h" #include 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" diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index 72dbd998edd8..769942d1729d 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -9,6 +9,7 @@ #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/tx.h" @@ -822,6 +823,105 @@ 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 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, + .reserved = {0}, + .snp = 0x18, + .microcode = 0xDB}; + h->put(milan_chip_id.hex_str(), 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::TcbVersion milan_x_tcb_version = { + .boot_loader = 0, + .tee = 0, + .reserved = {0}, + .snp = 0x18, + .microcode = 0x44}; + h->put(milan_x_chip_id.hex_str(), 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::TcbVersion genoa_tcb_version = { + .boot_loader = 0, + .tee = 0, + .reserved = {0}, + .snp = 0x17, + .microcode = 0x54}; + h->put(genoa_chip_id.hex_str(), 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::TcbVersion genoa_x_tcb_version = { + .boot_loader = 0, + .tee = 0, + .reserved = {0}, + .snp = 0x17, + .microcode = 0x4F}; + h->put(genoa_x_chip_id.hex_str(), genoa_x_tcb_version); + } + + static void trust_node_snp_tcb_version( + ccf::kv::Tx& tx, pal::snp::Attestation& attestation) + { + // Fall back to statically configured tcb versions + auto cpuid = pal::snp::get_cpuid(); + if (attestation.version < pal::snp::MIN_TCB_VERIF_VERSION) + { + 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.hex_str(), 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 a5576516a227..a52cd5dc2c72 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 SnpTcbVersionMap 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); } // diff --git a/tests/code_update.py b/tests/code_update.py index 806aabf23562..f79b545713ec 100644 --- a/tests/code_update.py +++ b/tests/code_update.py @@ -273,6 +273,61 @@ 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("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: + 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 TimeoutError as e: + thrown_exception = e + 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) + 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 +794,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/infra/consortium.py b/tests/infra/consortium.py index 19c4c2dccda8..53040559affd 100644 --- a/tests/infra/consortium.py +++ b/tests/infra/consortium.py @@ -890,6 +890,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": return self.remove_virtual_host_data(remote_node, host_data_key) @@ -914,6 +923,14 @@ 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( "set_node_data", 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;