Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check TCB version to ensure it is up to date enough #6837

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
Draft
6 changes: 6 additions & 0 deletions include/ccf/node/quote.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -22,6 +23,8 @@ namespace ccf
FailedInvalidHostData,
FailedInvalidQuotedPublicKey,
FailedUVMEndorsementsNotFound,
FailedInvalidCPUID,
FailedInvalidTcbVersion
};

class AttestationProvider
Expand All @@ -35,6 +38,9 @@ namespace ccf

static std::optional<HostData> get_host_data(const QuoteInfo& quote_info);

static std::optional<pal::snp::Attestation> get_snp_attestation(
const QuoteInfo& quote_info);

static QuoteVerificationResult verify_quote_against_store(
ccf::kv::ReadOnlyTx& tx,
const QuoteInfo& quote_info,
Expand Down
47 changes: 46 additions & 1 deletion include/ccf/pal/attestation_sev_snp.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,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
Expand Down Expand Up @@ -140,7 +142,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 */
Expand Down Expand Up @@ -260,4 +265,44 @@ 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;
};
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;
std::string hex_str() const
{
return fmt::format("{:02x}{:02x}{:02x}", family, model, stepping);
}
};
#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;
model.family = cpuid.base_family + cpuid.extended_family;
model.model = (cpuid.extended_model << 4) | cpuid.base_model;
model.stepping = cpuid.stepping;
return model;
}
}
19 changes: 19 additions & 0 deletions include/ccf/service/tables/tcb_verification.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// 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<pal::snp::AttestChipModel, pal::snp::TcbVersion>;

namespace Tables
{
static constexpr auto SNP_TCB_VERSIONS =
"public:ccf.gov.nodes.snp_tcb_versions";
}
}
3 changes: 3 additions & 0 deletions js/ccf-app/src/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions src/js/extensions/snp_attestation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
Expand Down
75 changes: 75 additions & 0 deletions src/node/quote.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -138,6 +139,29 @@ namespace ccf
return measurement;
}

std::optional<pal::snp::Attestation> 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<const pal::snp::Attestation*>(
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<HostData> AttestationProvider::get_host_data(
const QuoteInfo& quote_info)
{
Expand Down Expand Up @@ -231,6 +255,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<const pal::snp::Attestation*>(quote_info.quote.data());

if (attestation.version < pal::snp::MIN_TCB_VERIF_VERSION)
{
// 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<SnpTcbVersionMap>(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,
Expand Down Expand Up @@ -263,6 +332,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);
}
Expand Down
4 changes: 3 additions & 1 deletion src/node/rpc/member_frontend.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -508,7 +509,8 @@ namespace ccf
handle->foreach([&response_body](const auto& k, const auto& v) {
if constexpr (
std::is_same_v<typename T::Key, ccf::crypto::Sha256Hash> ||
pal::is_attestation_measurement<typename T::Key>::value)
pal::is_attestation_measurement<typename T::Key>::value ||
std::is_same_v<typename T::Key, ccf::pal::snp::AttestChipModel>)
Copy link
Contributor Author

@cjen1-msft cjen1-msft Feb 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is quite hacky. The issue is that there isn't an obvious string representation of the AttestChipModel, so it is a json key which then doesn't have obviously correct semantics for this getter.

{
response_body[k.hex_str()] = v;
}
Expand Down
7 changes: 7 additions & 0 deletions src/node/rpc/node_frontend.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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;
}

Expand Down
61 changes: 61 additions & 0 deletions src/service/internal_tables_access.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -822,6 +823,66 @@ namespace ccf
{{uvm_endorsements->feed, {uvm_endorsements->svn}}});
}

static void trust_static_snp_tcb_version(ccf::kv::Tx& tx)
{
auto h = tx.wo<ccf::SnpTcbVersionMap>(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 = {
.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,
.extended_model = 0x0,
.extended_family = 0x0A});
constexpr pal::snp::TcbVersion milan_x_tcb_version = {
.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,
.extended_model = 0x1,
.extended_family = 0x0A});
constexpr pal::snp::TcbVersion genoa_tcb_version = {
.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,
.extended_model = 0x1,
.extended_family = 0x0A});
constexpr pal::snp::TcbVersion genoa_x_tcb_version = {
.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, 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<ccf::SnpTcbVersionMap>(Tables::SNP_TCB_VERSIONS);
h->put(chip_id, attestation.reported_tcb);
}
}

static void init_configuration(
ccf::kv::Tx& tx, const ServiceConfiguration& configuration)
{
Expand Down
5 changes: 4 additions & 1 deletion src/service/network_tables.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
{
Expand All @@ -108,7 +110,8 @@ namespace ccf
virtual_measurements,
host_data,
snp_measurements,
snp_uvm_endorsements);
snp_uvm_endorsements,
snp_tcb_versions);
}

//
Expand Down
3 changes: 3 additions & 0 deletions tests/npm-app/src/endpoints/snp_attestation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading