Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

- Fixed a double free when setting a property on a JavaScript object fails, which application script could trigger while the request object was being built. Such failures are now reported as a failed request (#8356).
- Historical states retrieved by JavaScript endpoints, through `ccf.historicalState` or `ccf.historical.getStateRange`, remain available through response conversion and are released when the request completes, rather than being retained for the lifetime of the node (#8355).
- JavaScript `verifySnpAttestation()` and the deprecated C++ `ccf::pal::snp::Attestation` returned swapped `current_minor` and `current_build` values. Both now match the AMD SEV-SNP report layout, with `current_build` at offset `0x1E8` and `current_minor` at `0x1E9` (#8083).

### Changed

- SNP attestation reports are now parsed and verified through TAV. Decode a report with `ccf::pal::snp::parse_attestation_report_unverified()`, which returns `ccf::pal::snp::AttestationReport`, an owning smart pointer, and verify it against TAV and CCF's policy with `ccf::pal::verify_snp_attestation_report_and_get()`. Field accessors borrow the report's storage, so destroying or replacing the owner invalidates them. The packed `ccf::pal::snp::Attestation` wire-layout type and its accessors still work, but are deprecated (#8083).
- `ccf::pal::snp::get_attestation()` in `ccf/pal/snp_ioctl.h` is unchanged, but its `get()` accessor is deprecated. Call `get_raw()` instead for the unverified report bytes, then decode them with `parse_attestation_report_unverified()` (#8083).

## [7.0.15]

Expand Down
10 changes: 10 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,10 @@ add_ccf_static_library(
SRCS ${CCF_DIR}/src/pal/attestation.cpp
LINK_LIBS ccfcrypto
)
target_include_directories(
ccf_pal
PRIVATE ${CCF_DIR}/3rdparty/internal/tee-attestation-verification/ffi/include
)

# CCF js lib
add_ccf_static_library(
Expand Down Expand Up @@ -510,6 +514,11 @@ install(
PATTERN "*.inc"
)

install(
FILES ${TAV_INCLUDE_DIR}/tav/snp.h ${TAV_INCLUDE_DIR}/tav/utils.h
DESTINATION include/3rdparty/tav
)

# Install all private CCF headers, which may still be needed
install(
DIRECTORY src/
Expand Down Expand Up @@ -598,6 +607,7 @@ if(BUILD_TESTS)
snp_ioctl_test
${CMAKE_CURRENT_SOURCE_DIR}/src/pal/test/snp_ioctl_test.cpp
)
target_link_libraries(snp_ioctl_test PRIVATE ccf_pal)
set_property(TEST snp_ioctl_test APPEND PROPERTY LABELS snp)
set_property(TEST snp_ioctl_test APPEND PROPERTY CONFIGURATIONS snp)

Expand Down
19 changes: 19 additions & 0 deletions cmake/ccf_rs.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,29 @@ add_custom_target(
"${CCF_RS_DIR}/rust-toolchain.toml"
"${CCF_DIR}/src/cose/cose_rs/Cargo.toml"
"${CCF_DIR}/3rdparty/internal/cose-openssl/Cargo.toml"
"${CCF_DIR}/3rdparty/internal/tee-attestation-verification/ffi/Cargo.toml"
COMMENT
"Building ${CCF_RS_PACKAGE} Rust static library (Cargo profile: ${CCF_RS_CARGO_PROFILE_NAME})"
USES_TERMINAL
VERBATIM
)

add_library(ccf_rs INTERFACE)
target_link_libraries(
ccf_rs
INTERFACE
$<BUILD_INTERFACE:${CCF_RS_LIB_BUILD_PATH}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_PREFIX}/lib/${CCF_RS_LIB}>
ssl
crypto
)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_link_libraries(
ccf_rs
INTERFACE ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS} m
)
endif()
add_dependencies(ccf_rs cargo-build_ccf_rs)

install(FILES "${CCF_RS_LIB_BUILD_PATH}" DESTINATION lib)
install(TARGETS ccf_rs EXPORT ccf)
8 changes: 1 addition & 7 deletions cmake/crypto.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,7 @@ add_hardening(ccfcrypto)
add_tidy(ccfcrypto)

target_link_libraries(ccfcrypto PUBLIC crypto ssl ccf_threading)
target_link_libraries(
ccfcrypto
PUBLIC
$<BUILD_INTERFACE:${CCF_RS_LIB_BUILD_PATH}>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_PREFIX}/lib/${CCF_RS_LIB}>
)
add_dependencies(ccfcrypto cargo-build_ccf_rs)
target_link_libraries(ccfcrypto PUBLIC ccf_rs)
set_property(TARGET ccfcrypto PROPERTY POSITION_INDEPENDENT_CODE ON)

install(TARGETS ccfcrypto EXPORT ccf DESTINATION lib)
7 changes: 7 additions & 0 deletions include/ccf/node/quote.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,15 @@ namespace ccf

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

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
[[deprecated("Use get_snp_attestation_report")]]
static std::optional<pal::snp::Attestation> get_snp_attestation(
const QuoteInfo& quote_info);
#pragma GCC diagnostic pop

static std::optional<pal::snp::AttestationReport>
get_snp_attestation_report(const QuoteInfo& quote_info);

static QuoteVerificationResult verify_quote_against_store(
ccf::kv::ReadOnlyTx& tx,
Expand Down
7 changes: 7 additions & 0 deletions include/ccf/pal/attestation.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#pragma once

#include "ccf/ds/quote_info.h"
#include "ccf/pal/attestation_sev_snp.h"
#include "ccf/pal/attestation_sev_snp_endorsements.h"
#include "ccf/pal/measurement.h"
#include "ccf/pal/report_data.h"
Expand All @@ -28,6 +29,12 @@ namespace ccf::pal
PlatformAttestationMeasurement& measurement,
PlatformAttestationReportData& report_data);

/// Verify with TAV, then enforce CCF's SNP attestation policy.
snp::AttestationReport verify_snp_attestation_report_and_get(
const QuoteInfo& quote_info,
PlatformAttestationMeasurement& measurement,
PlatformAttestationReportData& report_data);

void verify_quote(
const QuoteInfo& quote_info,
PlatformAttestationMeasurement& measurement,
Expand Down
123 changes: 107 additions & 16 deletions include/ccf/pal/attestation_sev_snp.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@
#include <cstdint>
#include <cstring>
#include <map>
#include <memory>
#include <optional>
#include <span>
#include <stdexcept>
#include <string>
#include <string_view>
#include <tav/snp.h>
#include <vector>

namespace ccf::pal::snp
Expand All @@ -28,6 +32,7 @@ namespace ccf::pal::snp
static constexpr auto NO_SECURITY_POLICY = "";

// From https://developer.amd.com/sev/
[[deprecated("TAV verifies AMD root signing keys internally")]]
constexpr auto amd_milan_root_signing_public_key =
R"(-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0Ld52RJOdeiJlqK2JdsV
Expand All @@ -44,6 +49,7 @@ pCCoMNit2uLo9M18fHz10lOMT8nWAUvRZFzteXCm+7PHdYPlmQwUw3LvenJ/ILXo
QPHfbkH0CyPfhl1jWhJFZasCAwEAAQ==
-----END PUBLIC KEY-----
)";
[[deprecated("TAV verifies AMD root signing keys internally")]]
constexpr auto amd_genoa_root_signing_public_key =
R"(-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA3Cd95S/uFOuRIskW9vz9
Expand All @@ -60,6 +66,7 @@ HP1qYrnvhzaG1S70vw6OkbaaC9EjiH/uHgAJQGxon7u0Q7xgoREWA/e7JcBQwLg8
0Hq/sbRuqesxz7wBWSY254cCAwEAAQ==
-----END PUBLIC KEY-----
)";
[[deprecated("TAV verifies AMD root signing keys internally")]]
constexpr auto amd_turin_root_signing_public_key =
R"(-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwaAriB7EIuVc4ZB1wD3Y
Expand All @@ -77,12 +84,14 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ==
-----END PUBLIC KEY-----
)";

struct AmdRootSigningKey
struct [[deprecated(
"TAV verifies AMD root signing keys internally")]] AmdRootSigningKey
{
const char* public_key;
const char* issuer;
};

[[deprecated("TAV verifies AMD root signing keys internally")]]
inline const std::map<ProductName, AmdRootSigningKey> amd_root_signing_keys{
{ProductName::Milan,
{amd_milan_root_signing_public_key,
Expand Down Expand Up @@ -222,15 +231,14 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ==

TcbVersionRaw() = default;

TcbVersionRaw(const std::vector<uint8_t>& data)
TcbVersionRaw(std::span<const uint8_t> data)
{
if (data.size() != snp_tcb_version_size)
{
throw std::logic_error(
fmt::format("Invalid TCB version raw data size: {}", data.size()));
}
std::memcpy(
static_cast<void*>(underlying_data), data.data(), snp_tcb_version_size);
std::memcpy(underlying_data, data.data(), snp_tcb_version_size);
}

[[nodiscard]] std::vector<uint8_t> data() const
Expand Down Expand Up @@ -392,6 +400,10 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ==
sizeof(PlatformInfo) == sizeof(uint64_t),
"Cannot cast PlatformInfo to uint64_t");

static constexpr size_t attestation_report_size = 1184;

struct [[deprecated("Use ccf::pal::snp::AttestationReport")]] Attestation;

#pragma pack(push, 1)
// Table 21

Expand Down Expand Up @@ -425,8 +437,8 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ==
uint8_t reserved1[21] = {0}; /* 0x18B */
uint8_t chip_id[64] = {0}; /* 0x1A0 */
TcbVersionRaw committed_tcb; /* 0x1E0 */
uint8_t current_minor = 0; /* 0x1E8 */
uint8_t current_build = 0; /* 0x1E9 */
uint8_t current_build = 0; /* 0x1E8 */
uint8_t current_minor = 0; /* 0x1E9 */
uint8_t current_major = 0; /* 0x1EA */
uint8_t reserved2 = 0; /* 0x1EB */
uint8_t committed_build = 0; /* 0x1EC */
Expand Down Expand Up @@ -456,6 +468,49 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ==
};
#pragma pack(pop)

// Reports are allocated in Rust and must be freed through TAV, not C++
// delete. A stateless deleter keeps the smart pointer default-constructible
// without storing a cleanup function pointer.
struct AttestationReportDeleter
{
void operator()(TavSnpAttestationReport* report) const noexcept
{
tav_snp_attestation_report_free(report);
}
};

using AttestationReport =
std::unique_ptr<TavSnpAttestationReport, AttestationReportDeleter>;

inline std::span<const uint8_t> get_chip_id_for_vcek(
const AttestationReport& report)
{
if (report == nullptr)
{
throw std::logic_error("Cannot access an empty SNP attestation report");
}
const uint8_t* data = nullptr;
size_t size = 0;
tav_snp_attestation_report_chip_id(report.get(), &data, &size);
const auto chip_id = std::span<const uint8_t>{data, size};
const auto product = get_sev_snp_product(
tav_snp_attestation_report_cpuid_fam_id(report.get()),
tav_snp_attestation_report_cpuid_mod_id(report.get()));
if (product == ProductName::Milan || product == ProductName::Genoa)
{
return chip_id;
}
if (product == ProductName::Turin)
{
return chip_id.first(8);
}
throw std::logic_error(
fmt::format("Unsupported SEV-SNP product: {}", product));
}

[[nodiscard]] AttestationReport parse_attestation_report_unverified(
std::span<const uint8_t> report);

static HostPort get_endpoint_loc(
const EndorsementsServer& server, const HostPort& default_values)
{
Expand All @@ -475,24 +530,37 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ==

static EndorsementEndpointsConfiguration
make_endorsement_endpoint_configuration(
const Attestation& quote,
const AttestationReport& quote,
const snp::EndorsementsServers& endorsements_servers = {})
{
if (quote.version < minimum_attestation_version)
if (quote == nullptr)
{
throw std::logic_error("Cannot access an empty SNP attestation report");
}
if (
tav_snp_attestation_report_version(quote.get()) <
minimum_attestation_version)
{
throw std::logic_error(fmt::format(
"SEV-SNP: attestation version {} is not supported. Minimum "
"supported version is {}",
quote.version,
tav_snp_attestation_report_version(quote.get()),
minimum_attestation_version));
}

EndorsementEndpointsConfiguration config;

auto chip_id_hex =
fmt::format("{:02x}", fmt::join(quote.get_chip_id_for_vcek(), ""));
fmt::format("{:02x}", fmt::join(get_chip_id_for_vcek(quote), ""));
const uint8_t* reported_tcb_data = nullptr;
size_t reported_tcb_size = 0;
tav_snp_attestation_report_reported_tcb(
quote.get(), &reported_tcb_data, &reported_tcb_size);
const auto reported_tcb_raw =
std::span<const uint8_t>{reported_tcb_data, reported_tcb_size};
auto reported_tcb = fmt::format(
"{:0x}", *reinterpret_cast<const uint64_t*>(&quote.reported_tcb));
"{:02x}",
fmt::join(reported_tcb_raw.rbegin(), reported_tcb_raw.rend(), ""));

constexpr size_t default_max_retries_count = 10;
static const ds::SizeString default_max_client_response_size =
Expand Down Expand Up @@ -533,8 +601,9 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ==
}
case EndorsementsEndpointType::AMD:
{
auto product =
get_sev_snp_product(quote.cpuid_fam_id, quote.cpuid_mod_id);
auto product = get_sev_snp_product(
tav_snp_attestation_report_cpuid_fam_id(quote.get()),
tav_snp_attestation_report_cpuid_mod_id(quote.get()));

std::string boot_loader;
std::string tee;
Expand All @@ -546,7 +615,9 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ==
case ProductName::Milan:
case ProductName::Genoa:
{
auto tcb = quote.reported_tcb.to_policy(product).to_milan_genoa();
auto tcb = TcbVersionRaw(reported_tcb_raw)
.to_policy(product)
.to_milan_genoa();
boot_loader = fmt::format("{}", tcb.boot_loader);
tee = fmt::format("{}", tcb.tee);
snp = fmt::format("{}", tcb.snp);
Expand All @@ -555,7 +626,8 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ==
}
case ProductName::Turin:
{
auto tcb = quote.reported_tcb.to_policy(product).to_turin();
auto tcb =
TcbVersionRaw(reported_tcb_raw).to_policy(product).to_turin();
boot_loader = fmt::format("{}", tcb.boot_loader);
tee = fmt::format("{}", tcb.tee);
snp = fmt::format("{}", tcb.snp);
Expand Down Expand Up @@ -608,14 +680,33 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ==
return config;
}

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
[[deprecated("Use the AttestationReport overload")]]
static EndorsementEndpointsConfiguration
make_endorsement_endpoint_configuration(
const Attestation& quote,
const snp::EndorsementsServers& endorsements_servers = {})
{
const auto* report = reinterpret_cast<const uint8_t*>(&quote);
return make_endorsement_endpoint_configuration(
parse_attestation_report_unverified({report, attestation_report_size}),
endorsements_servers);
}

class AttestationInterface
{
public:
[[nodiscard]] virtual const snp::Attestation& get() const = 0;
[[deprecated(
"Use get_raw() and explicitly decode with "
"parse_attestation_report_unverified")]] [[nodiscard]] virtual const snp::
Attestation&
get() const = 0;
virtual std::vector<uint8_t> get_raw() = 0;

virtual ~AttestationInterface() = default;
};
#pragma GCC diagnostic pop

}

Expand Down
2 changes: 2 additions & 0 deletions include/ccf/pal/snp_ioctl.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ namespace ccf::pal::snp
return ioctl6::supports_sev_snp();
}

// Acquire an attestation object. get_raw() returns owned, unverified bytes;
// decode them explicitly with parse_attestation_report_unverified().
static std::unique_ptr<AttestationInterface> get_attestation(
const PlatformAttestationReportData& report_data)
{
Expand Down
Loading