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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Added

- Support for COSE-only receipts in snapshots to support #7711). #7712
- Support for Turin attestations (#7295, #7264, #7449, #7748, #7749)

## [6.0.24]

Expand Down
11 changes: 11 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,17 @@ endif()
# Add sample apps
add_subdirectory(${CCF_DIR}/samples)

# SNP attestation fetching and verification binary
add_test_bin(
verify_attestation
${CMAKE_CURRENT_SOURCE_DIR}/src/pal/test/verify_attestation.cpp
)
target_link_libraries(
verify_attestation PRIVATE ccf_pal.host ccfcrypto.host uv curl
http_parser.host
)
install(TARGETS verify_attestation DESTINATION bin)

if(BUILD_TESTS)
enable_testing()

Expand Down
24 changes: 20 additions & 4 deletions doc/operations/platforms/snp.rst
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,30 @@ To set the minimum TCB version for a specific CPU model, you can use the followi
"name": "set_snp_minimum_tcb_version_hex",
"args": {
"cpuid": "00a00f11",
"tcb_version": "d315000000000004"
"tcb_version": "db18000000000004"
}
}
]
}

The parsed TCB version mapped to that cpuid in the :ref:`audit/builtin_maps:``nodes.snp.tcb_versions``` table, which is used to validate the TCB version of joining nodes.

.. note::
`Milan <https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/general-purpose/dcasv5-series>`__
and `Genoa <https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/general-purpose/dcasv6-series>`__
are currently deployed in Azure Container Instances.
As of March 2026, reasonable minimum values are:

+-------+----------+---------------------+
| Model | CPUID | Minimum TCB Version |
+=======+==========+=====================+
| Milan | 00a00f11 | db18000000000004 |
+-------+----------+---------------------+
| Genoa | 00a10f11 | 541700000000000a |
+-------+----------+---------------------+
| Turin | 00b00f21 | 5100000004010101 |
+-------+----------+---------------------+

.. note::
The CPUID and TCB version must be input as lower-case hex-strings. The values in the above example are for Milan CPUs, and can be expanded as follows:

Expand All @@ -126,17 +142,17 @@ The parsed TCB version mapped to that cpuid in the :ref:`audit/builtin_maps:``no

SNP attestation structures contain the combined Family (``Extended Family + Base Family``) and Model (``Extended Model : Base Model``) values, so 25 (0x19) and 1 (0x01) respectively for the above Milan example.

The above TCB version ``d315000000000004`` is for a Milan CPU.
The TCB version ``db18000000000004`` is for a Milan CPU.
It, and also TCB versions for Genoa CPUs, can be expanded as follows:

+-------------------+------------------+
| | Value |
| TCB Version Field +-----+------------+
| | dec | hex |
+===================+=====+============+
| Microcode | 211 | 0xd3 |
| Microcode | 219 | 0xdb |
+-------------------+-----+------------+
| SNP | 21 | 0x15 |
| SNP | 24 | 0x18 |
+-------------------+-----+------------+
| Reserved | 0 | 0x00000000 |
+-------------------+-----+------------+
Expand Down
31 changes: 26 additions & 5 deletions include/ccf/pal/attestation_sev_snp.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,7 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ==
inline const std::map<ProductName, const char*> amd_root_signing_keys{
{ProductName::Milan, amd_milan_root_signing_public_key},
{ProductName::Genoa, amd_genoa_root_signing_public_key},
// Disabled until we can test this
//{ProductName::turin, amd_turin_root_signing_public_key},
{ProductName::Turin, amd_turin_root_signing_public_key},
};

#pragma pack(push, 1)
Expand Down Expand Up @@ -417,6 +416,23 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ==
TcbVersionRaw launch_tcb; /* 0x1F0 */
uint8_t reserved4[168]; /* 0x1F8 */
struct Signature signature; /* 0x2A0 */

[[nodiscard]] std::span<const uint8_t> get_chip_id_for_vcek() const
{
auto product = get_sev_snp_product(cpuid_fam_id, cpuid_mod_id);
if (product == ProductName::Milan || product == ProductName::Genoa)
{
return {chip_id, sizeof(chip_id)};
}
// On Turin only the first 8 bytes are used for the chip ID
// VCEK certificate and KDS interface spec section 3.1
if (product == ProductName::Turin)
{
return {chip_id, 8};
}
throw std::logic_error(
fmt::format("Unsupported SEV-SNP product: {}", product));
}
};
#pragma pack(pop)

Expand Down Expand Up @@ -456,8 +472,10 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ==

EndorsementEndpointsConfiguration config;

auto chip_id_hex = fmt::format("{:02x}", fmt::join(quote.chip_id, ""));
auto reported_tcb = fmt::format("{:0x}", *(uint64_t*)(&quote.reported_tcb));
auto chip_id_hex =
fmt::format("{:02x}", fmt::join(quote.get_chip_id_for_vcek(), ""));
auto reported_tcb = fmt::format(
"{:0x}", *reinterpret_cast<const uint64_t*>(&quote.reported_tcb));

constexpr size_t default_max_retries_count = 10;
static const ds::SizeString default_max_client_response_size =
Expand Down Expand Up @@ -505,6 +523,7 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ==
std::string tee;
std::string snp;
std::string microcode;
std::optional<std::string> fmc = std::nullopt;
switch (product)
{
case ProductName::Milan:
Expand All @@ -524,6 +543,7 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ==
tee = fmt::format("{}", tcb.tee);
snp = fmt::format("{}", tcb.snp);
microcode = fmt::format("{}", tcb.microcode);
fmc = fmt::format("{}", tcb.fmc);
break;
}
default:
Expand All @@ -544,7 +564,8 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ==
microcode,
product,
max_retries_count,
max_client_response_size));
max_client_response_size,
fmc));
break;
}
case EndorsementsEndpointType::THIM:
Expand Down
7 changes: 6 additions & 1 deletion include/ccf/pal/attestation_sev_snp_endorsements.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,18 @@ namespace ccf::pal::snp
const std::string& microcode,
const ProductName& product_name,
size_t max_retries_count,
size_t max_client_response_size)
size_t max_client_response_size,
const std::optional<std::string>& fmc_version = std::nullopt)
{
std::map<std::string, std::string> params;
params["blSPL"] = boot_loader;
params["teeSPL"] = tee;
params["snpSPL"] = snp;
params["ucodeSPL"] = microcode;
if (fmc_version.has_value())
{
params["fmcSPL"] = fmc_version.value();
}

EndorsementEndpointsConfiguration::Server server;
EndorsementEndpointsConfiguration::EndpointInfo leaf{
Expand Down
13 changes: 10 additions & 3 deletions include/ccf/pal/sev_snp_cpuid.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ namespace ccf::pal::snp
#pragma pack(push, 1)
// AMD CPUID specification. Chapter 2 Fn0000_0001_EAX
// Milan: 0x00A00F11
// Genoa: 0X00A10F11
// Genoa: 0x00A10F11
// Turin: 0x00B00F21
// Note: The CPUID is little-endian so the hex_string is reversed
struct CPUID
{
Expand Down Expand Up @@ -130,7 +131,7 @@ namespace ccf::pal::snp
return ProductName::Genoa;
}
constexpr uint8_t turin_family = 0x1A;
constexpr uint8_t turin_model = 0x01;
constexpr uint8_t turin_model = 0x02;
if (family == turin_family && model == turin_model)
{
return ProductName::Turin;
Expand All @@ -149,11 +150,17 @@ namespace ccf::pal::snp
switch (product)
{
case ProductName::Milan:
// See Table 2 of "Revision Guide for 19h 00h-0Fh Processors"
// https://www.amd.com/content/dam/amd/en/documents/processor-tech-docs/revision-guides/56683.pdf
return "00a00f11";
case ProductName::Genoa:
// See Table 2 of "Revision Guide for 19h 10h-1Fh Processors"
// https://www.amd.com/content/dam/amd/en/documents/processor-tech-docs/revision-guides/57095-PUB_1_01.pdf
return "00a10f11";
case ProductName::Turin:
return "00b00f11";
// See Table 2 of "Revision Guide for 1Ah 00h-0Fh Processors"
// https://www.amd.com/content/dam/amd/en/documents/processor-tech-docs/revision-guides/58251.pdf
return "00b00f21";
default:
throw std::logic_error(fmt::format(
"SEV-SNP: Unsupported product for CPUID: {}", to_string(product)));
Expand Down
57 changes: 46 additions & 11 deletions scripts/fetch_amd_collateral.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Licensed under the Apache 2.0 License.

import argparse
from enum import Enum
import logging
import sys
import httpx
Expand All @@ -12,10 +13,16 @@
import json


class AMDCPUFamily(Enum):
MILAN = "Milan"
GENOA = "Genoa"
TURIN = "Turin"


def make_host_amd_blob(tcbm, leaf, chain):
return json.dumps(
{
"cacheControl": "0",
"cacheControl": 0,
"tcbm": tcbm.upper(),
"vcekCert": leaf,
"certificateChain": chain,
Expand All @@ -24,15 +31,42 @@ def make_host_amd_blob(tcbm, leaf, chain):


def make_leaf_url(base_url, product_family, chip_id, tcbm):
microcode = int(tcbm[0:2], base=16)
snp = int(tcbm[2:4], base=16)
# 4 reserved bytes
tee = int(tcbm[12:14], base=16)
bootloader = int(tcbm[14:16], base=16)

return (
f"{base_url}/vcek/v1/{product_family}/{chip_id}"
+ f"?blSPL={bootloader}&teeSPL={tee}&snpSPL={snp}&ucodeSPL={microcode}"
if len(tcbm) != 16:
raise ValueError("TCBM must be 16 hex characters (64 bits)")

if product_family in [AMDCPUFamily.MILAN.value, AMDCPUFamily.GENOA.value]:
assert len(chip_id) == 64 * 2, "Chip ID must be 64 bytes long"
hwid = chip_id[0 : 64 * 2]
params = {
"ucodeSPL": int(tcbm[0:2], base=16),
"snpSPL": int(tcbm[2:4], base=16),
# 4 reserved bytes
"teeSPL": int(tcbm[12:14], base=16),
"blSPL": int(tcbm[14:16], base=16),
}
elif product_family == AMDCPUFamily.TURIN.value:
# Note hwid is explicitly shortened for turin (the full chip_id in the attestation will not work)
# See Table 11 (section 3.1) of the VCEK spec for details
assert (
len(chip_id) >= 8 * 2
), "Chip ID should be at least 8 bytes long for Turin"
hwid = chip_id[0 : 8 * 2]
assert chip_id[8 * 2 :] == "0" * (
len(chip_id) - len(hwid)
), "Chip ID bytes 8-64 should be zero for Turin"
params = {
"ucodeSPL": int(tcbm[0:2], base=16),
# 3 reserved bytes
"snpSPL": int(tcbm[8:10], base=16),
"teeSPL": int(tcbm[10:12], base=16),
"blSPL": int(tcbm[12:14], base=16),
"fmcSPL": int(tcbm[14:16], base=16),
}
else:
raise ValueError(f"Unknown product family {product_family}")

return f"{base_url}/vcek/v1/{product_family}/{hwid}?" + "&".join(
[f"{k}={v}" for k, v in params.items()]
)


Expand Down Expand Up @@ -63,7 +97,8 @@ def make_chain_url(base_url, product_family):
"--product-family",
type=str,
default="Milan",
help="AMD product family (e.g., Milan, Genoa).",
choices=[pf.value for pf in AMDCPUFamily],
help="AMD product family",
)
parser.add_argument(
"--output",
Expand Down
4 changes: 3 additions & 1 deletion src/http/error_reporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Licensed under the Apache 2.0 License.
#pragma once

#include "ccf/rpc_context.h"

namespace http
{
class ErrorReporter
Expand All @@ -14,4 +16,4 @@ namespace http
virtual void report_request_header_too_large_error(
const ccf::ListenInterfaceID&) = 0;
};
}
}
4 changes: 1 addition & 3 deletions src/node/node_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -530,9 +530,7 @@ namespace ccf
}
// On SEV-SNP, fetch endorsements from servers if specified
quote_endorsements_client = std::make_shared<QuoteEndorsementsClient>(
rpcsessions,
endpoint_config,
[this](std::vector<uint8_t>&& endorsements) {
endpoint_config, [this](std::vector<uint8_t>&& endorsements) {
std::lock_guard<pal::Mutex> guard(lock);
quote_info.endorsements = std::move(endorsements);
try
Expand Down
8 changes: 3 additions & 5 deletions src/node/quote_endorsements_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
// Licensed under the Apache 2.0 License.
#pragma once

#include "ccf/crypto/verifier.h"
#include "ccf/http_consts.h"
#include "ccf/pal/attestation.h"
#include "ccf/pal/attestation_sev_snp_endorsements.h"
#include "ccf/pal/locking.h"
#include "ds/thread_messaging.h"
#include "enclave/rpc_sessions.h"
#include "http/curl.h"

#include <curl/curl.h>
Expand Down Expand Up @@ -43,8 +45,6 @@ namespace ccf
static constexpr size_t server_connection_timeout_s = 3;
static constexpr size_t server_response_timeout_s = 3;

std::shared_ptr<RPCSessions> rpcsessions;

const pal::snp::EndorsementEndpointsConfiguration config;
QuoteEndorsementsFetchedCallback done_cb;

Expand Down Expand Up @@ -341,10 +341,8 @@ namespace ccf

public:
QuoteEndorsementsClient(
const std::shared_ptr<RPCSessions>& rpcsessions_,
const pal::snp::EndorsementEndpointsConfiguration& config_,
QuoteEndorsementsFetchedCallback cb) :
rpcsessions(rpcsessions_),
config(config_),
done_cb(cb) {};

Expand Down
Loading
Loading