Skip to content

Commit 872919b

Browse files
cjen1-msfteddyashtonachamayouCopilot
authored
[release/6.x] Backport: Turin support (#7295, #7264, #7449, #7748, #7749) (#7752)
Co-authored-by: Eddy Ashton <ashton.eddy@gmail.com> Co-authored-by: Amaury Chamayou <amaury@xargs.fr> Co-authored-by: Amaury Chamayou <amchamay@microsoft.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent ee96ff2 commit 872919b

14 files changed

Lines changed: 597 additions & 112 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1212
### Added
1313

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

1617
## [6.0.24]
1718

CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,17 @@ endif()
748748
# Add sample apps
749749
add_subdirectory(${CCF_DIR}/samples)
750750

751+
# SNP attestation fetching and verification binary
752+
add_test_bin(
753+
verify_attestation
754+
${CMAKE_CURRENT_SOURCE_DIR}/src/pal/test/verify_attestation.cpp
755+
)
756+
target_link_libraries(
757+
verify_attestation PRIVATE ccf_pal.host ccfcrypto.host uv curl
758+
http_parser.host
759+
)
760+
install(TARGETS verify_attestation DESTINATION bin)
761+
751762
if(BUILD_TESTS)
752763
enable_testing()
753764

doc/operations/platforms/snp.rst

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,30 @@ To set the minimum TCB version for a specific CPU model, you can use the followi
9393
"name": "set_snp_minimum_tcb_version_hex",
9494
"args": {
9595
"cpuid": "00a00f11",
96-
"tcb_version": "d315000000000004"
96+
"tcb_version": "db18000000000004"
9797
}
9898
}
9999
]
100100
}
101101
102102
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.
103103

104+
.. note::
105+
`Milan <https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/general-purpose/dcasv5-series>`__
106+
and `Genoa <https://learn.microsoft.com/en-us/azure/virtual-machines/sizes/general-purpose/dcasv6-series>`__
107+
are currently deployed in Azure Container Instances.
108+
As of March 2026, reasonable minimum values are:
109+
110+
+-------+----------+---------------------+
111+
| Model | CPUID | Minimum TCB Version |
112+
+=======+==========+=====================+
113+
| Milan | 00a00f11 | db18000000000004 |
114+
+-------+----------+---------------------+
115+
| Genoa | 00a10f11 | 541700000000000a |
116+
+-------+----------+---------------------+
117+
| Turin | 00b00f21 | 5100000004010101 |
118+
+-------+----------+---------------------+
119+
104120
.. note::
105121
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:
106122

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

127143
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.
128144

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

132148
+-------------------+------------------+
133149
| | Value |
134150
| TCB Version Field +-----+------------+
135151
| | dec | hex |
136152
+===================+=====+============+
137-
| Microcode | 211 | 0xd3 |
153+
| Microcode | 219 | 0xdb |
138154
+-------------------+-----+------------+
139-
| SNP | 21 | 0x15 |
155+
| SNP | 24 | 0x18 |
140156
+-------------------+-----+------------+
141157
| Reserved | 0 | 0x00000000 |
142158
+-------------------+-----+------------+

include/ccf/pal/attestation_sev_snp.h

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,7 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ==
8080
inline const std::map<ProductName, const char*> amd_root_signing_keys{
8181
{ProductName::Milan, amd_milan_root_signing_public_key},
8282
{ProductName::Genoa, amd_genoa_root_signing_public_key},
83-
// Disabled until we can test this
84-
//{ProductName::turin, amd_turin_root_signing_public_key},
83+
{ProductName::Turin, amd_turin_root_signing_public_key},
8584
};
8685

8786
#pragma pack(push, 1)
@@ -417,6 +416,23 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ==
417416
TcbVersionRaw launch_tcb; /* 0x1F0 */
418417
uint8_t reserved4[168]; /* 0x1F8 */
419418
struct Signature signature; /* 0x2A0 */
419+
420+
[[nodiscard]] std::span<const uint8_t> get_chip_id_for_vcek() const
421+
{
422+
auto product = get_sev_snp_product(cpuid_fam_id, cpuid_mod_id);
423+
if (product == ProductName::Milan || product == ProductName::Genoa)
424+
{
425+
return {chip_id, sizeof(chip_id)};
426+
}
427+
// On Turin only the first 8 bytes are used for the chip ID
428+
// VCEK certificate and KDS interface spec section 3.1
429+
if (product == ProductName::Turin)
430+
{
431+
return {chip_id, 8};
432+
}
433+
throw std::logic_error(
434+
fmt::format("Unsupported SEV-SNP product: {}", product));
435+
}
420436
};
421437
#pragma pack(pop)
422438

@@ -456,8 +472,10 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ==
456472

457473
EndorsementEndpointsConfiguration config;
458474

459-
auto chip_id_hex = fmt::format("{:02x}", fmt::join(quote.chip_id, ""));
460-
auto reported_tcb = fmt::format("{:0x}", *(uint64_t*)(&quote.reported_tcb));
475+
auto chip_id_hex =
476+
fmt::format("{:02x}", fmt::join(quote.get_chip_id_for_vcek(), ""));
477+
auto reported_tcb = fmt::format(
478+
"{:0x}", *reinterpret_cast<const uint64_t*>(&quote.reported_tcb));
461479

462480
constexpr size_t default_max_retries_count = 10;
463481
static const ds::SizeString default_max_client_response_size =
@@ -505,6 +523,7 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ==
505523
std::string tee;
506524
std::string snp;
507525
std::string microcode;
526+
std::optional<std::string> fmc = std::nullopt;
508527
switch (product)
509528
{
510529
case ProductName::Milan:
@@ -524,6 +543,7 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ==
524543
tee = fmt::format("{}", tcb.tee);
525544
snp = fmt::format("{}", tcb.snp);
526545
microcode = fmt::format("{}", tcb.microcode);
546+
fmc = fmt::format("{}", tcb.fmc);
527547
break;
528548
}
529549
default:
@@ -544,7 +564,8 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ==
544564
microcode,
545565
product,
546566
max_retries_count,
547-
max_client_response_size));
567+
max_client_response_size,
568+
fmc));
548569
break;
549570
}
550571
case EndorsementsEndpointType::THIM:

include/ccf/pal/attestation_sev_snp_endorsements.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,13 +132,18 @@ namespace ccf::pal::snp
132132
const std::string& microcode,
133133
const ProductName& product_name,
134134
size_t max_retries_count,
135-
size_t max_client_response_size)
135+
size_t max_client_response_size,
136+
const std::optional<std::string>& fmc_version = std::nullopt)
136137
{
137138
std::map<std::string, std::string> params;
138139
params["blSPL"] = boot_loader;
139140
params["teeSPL"] = tee;
140141
params["snpSPL"] = snp;
141142
params["ucodeSPL"] = microcode;
143+
if (fmc_version.has_value())
144+
{
145+
params["fmcSPL"] = fmc_version.value();
146+
}
142147

143148
EndorsementEndpointsConfiguration::Server server;
144149
EndorsementEndpointsConfiguration::EndpointInfo leaf{

include/ccf/pal/sev_snp_cpuid.h

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ namespace ccf::pal::snp
1515
#pragma pack(push, 1)
1616
// AMD CPUID specification. Chapter 2 Fn0000_0001_EAX
1717
// Milan: 0x00A00F11
18-
// Genoa: 0X00A10F11
18+
// Genoa: 0x00A10F11
19+
// Turin: 0x00B00F21
1920
// Note: The CPUID is little-endian so the hex_string is reversed
2021
struct CPUID
2122
{
@@ -130,7 +131,7 @@ namespace ccf::pal::snp
130131
return ProductName::Genoa;
131132
}
132133
constexpr uint8_t turin_family = 0x1A;
133-
constexpr uint8_t turin_model = 0x01;
134+
constexpr uint8_t turin_model = 0x02;
134135
if (family == turin_family && model == turin_model)
135136
{
136137
return ProductName::Turin;
@@ -149,11 +150,17 @@ namespace ccf::pal::snp
149150
switch (product)
150151
{
151152
case ProductName::Milan:
153+
// See Table 2 of "Revision Guide for 19h 00h-0Fh Processors"
154+
// https://www.amd.com/content/dam/amd/en/documents/processor-tech-docs/revision-guides/56683.pdf
152155
return "00a00f11";
153156
case ProductName::Genoa:
157+
// See Table 2 of "Revision Guide for 19h 10h-1Fh Processors"
158+
// https://www.amd.com/content/dam/amd/en/documents/processor-tech-docs/revision-guides/57095-PUB_1_01.pdf
154159
return "00a10f11";
155160
case ProductName::Turin:
156-
return "00b00f11";
161+
// See Table 2 of "Revision Guide for 1Ah 00h-0Fh Processors"
162+
// https://www.amd.com/content/dam/amd/en/documents/processor-tech-docs/revision-guides/58251.pdf
163+
return "00b00f21";
157164
default:
158165
throw std::logic_error(fmt::format(
159166
"SEV-SNP: Unsupported product for CPUID: {}", to_string(product)));

scripts/fetch_amd_collateral.py

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# Licensed under the Apache 2.0 License.
33

44
import argparse
5+
from enum import Enum
56
import logging
67
import sys
78
import httpx
@@ -12,10 +13,16 @@
1213
import json
1314

1415

16+
class AMDCPUFamily(Enum):
17+
MILAN = "Milan"
18+
GENOA = "Genoa"
19+
TURIN = "Turin"
20+
21+
1522
def make_host_amd_blob(tcbm, leaf, chain):
1623
return json.dumps(
1724
{
18-
"cacheControl": "0",
25+
"cacheControl": 0,
1926
"tcbm": tcbm.upper(),
2027
"vcekCert": leaf,
2128
"certificateChain": chain,
@@ -24,15 +31,42 @@ def make_host_amd_blob(tcbm, leaf, chain):
2431

2532

2633
def make_leaf_url(base_url, product_family, chip_id, tcbm):
27-
microcode = int(tcbm[0:2], base=16)
28-
snp = int(tcbm[2:4], base=16)
29-
# 4 reserved bytes
30-
tee = int(tcbm[12:14], base=16)
31-
bootloader = int(tcbm[14:16], base=16)
32-
33-
return (
34-
f"{base_url}/vcek/v1/{product_family}/{chip_id}"
35-
+ f"?blSPL={bootloader}&teeSPL={tee}&snpSPL={snp}&ucodeSPL={microcode}"
34+
if len(tcbm) != 16:
35+
raise ValueError("TCBM must be 16 hex characters (64 bits)")
36+
37+
if product_family in [AMDCPUFamily.MILAN.value, AMDCPUFamily.GENOA.value]:
38+
assert len(chip_id) == 64 * 2, "Chip ID must be 64 bytes long"
39+
hwid = chip_id[0 : 64 * 2]
40+
params = {
41+
"ucodeSPL": int(tcbm[0:2], base=16),
42+
"snpSPL": int(tcbm[2:4], base=16),
43+
# 4 reserved bytes
44+
"teeSPL": int(tcbm[12:14], base=16),
45+
"blSPL": int(tcbm[14:16], base=16),
46+
}
47+
elif product_family == AMDCPUFamily.TURIN.value:
48+
# Note hwid is explicitly shortened for turin (the full chip_id in the attestation will not work)
49+
# See Table 11 (section 3.1) of the VCEK spec for details
50+
assert (
51+
len(chip_id) >= 8 * 2
52+
), "Chip ID should be at least 8 bytes long for Turin"
53+
hwid = chip_id[0 : 8 * 2]
54+
assert chip_id[8 * 2 :] == "0" * (
55+
len(chip_id) - len(hwid)
56+
), "Chip ID bytes 8-64 should be zero for Turin"
57+
params = {
58+
"ucodeSPL": int(tcbm[0:2], base=16),
59+
# 3 reserved bytes
60+
"snpSPL": int(tcbm[8:10], base=16),
61+
"teeSPL": int(tcbm[10:12], base=16),
62+
"blSPL": int(tcbm[12:14], base=16),
63+
"fmcSPL": int(tcbm[14:16], base=16),
64+
}
65+
else:
66+
raise ValueError(f"Unknown product family {product_family}")
67+
68+
return f"{base_url}/vcek/v1/{product_family}/{hwid}?" + "&".join(
69+
[f"{k}={v}" for k, v in params.items()]
3670
)
3771

3872

@@ -63,7 +97,8 @@ def make_chain_url(base_url, product_family):
6397
"--product-family",
6498
type=str,
6599
default="Milan",
66-
help="AMD product family (e.g., Milan, Genoa).",
100+
choices=[pf.value for pf in AMDCPUFamily],
101+
help="AMD product family",
67102
)
68103
parser.add_argument(
69104
"--output",

src/http/error_reporter.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Licensed under the Apache 2.0 License.
33
#pragma once
44

5+
#include "ccf/rpc_context.h"
6+
57
namespace http
68
{
79
class ErrorReporter
@@ -14,4 +16,4 @@ namespace http
1416
virtual void report_request_header_too_large_error(
1517
const ccf::ListenInterfaceID&) = 0;
1618
};
17-
}
19+
}

src/node/node_state.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -530,9 +530,7 @@ namespace ccf
530530
}
531531
// On SEV-SNP, fetch endorsements from servers if specified
532532
quote_endorsements_client = std::make_shared<QuoteEndorsementsClient>(
533-
rpcsessions,
534-
endpoint_config,
535-
[this](std::vector<uint8_t>&& endorsements) {
533+
endpoint_config, [this](std::vector<uint8_t>&& endorsements) {
536534
std::lock_guard<pal::Mutex> guard(lock);
537535
quote_info.endorsements = std::move(endorsements);
538536
try

src/node/quote_endorsements_client.h

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
// Licensed under the Apache 2.0 License.
33
#pragma once
44

5+
#include "ccf/crypto/verifier.h"
6+
#include "ccf/http_consts.h"
57
#include "ccf/pal/attestation.h"
68
#include "ccf/pal/attestation_sev_snp_endorsements.h"
9+
#include "ccf/pal/locking.h"
710
#include "ds/thread_messaging.h"
8-
#include "enclave/rpc_sessions.h"
911
#include "http/curl.h"
1012

1113
#include <curl/curl.h>
@@ -43,8 +45,6 @@ namespace ccf
4345
static constexpr size_t server_connection_timeout_s = 3;
4446
static constexpr size_t server_response_timeout_s = 3;
4547

46-
std::shared_ptr<RPCSessions> rpcsessions;
47-
4848
const pal::snp::EndorsementEndpointsConfiguration config;
4949
QuoteEndorsementsFetchedCallback done_cb;
5050

@@ -341,10 +341,8 @@ namespace ccf
341341

342342
public:
343343
QuoteEndorsementsClient(
344-
const std::shared_ptr<RPCSessions>& rpcsessions_,
345344
const pal::snp::EndorsementEndpointsConfiguration& config_,
346345
QuoteEndorsementsFetchedCallback cb) :
347-
rpcsessions(rpcsessions_),
348346
config(config_),
349347
done_cb(cb) {};
350348

0 commit comments

Comments
 (0)