From c3870f61774e1670154a13c531bb89c42aa317ea Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Wed, 15 Jul 2026 11:55:31 +0100 Subject: [PATCH 01/14] Use TAV for SNP attestation verification Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2ec11cce-7838-47b5-be19-21425a3a3bbf --- CMakeLists.txt | 6 +- cmake/ccf_rs.cmake | 1 + src/pal/attestation.cpp | 129 ++----- src/pal/test/snp_attestation_validation.cpp | 32 +- src/rust/Cargo.lock | 353 +++++++++++++++++++- src/rust/Cargo.toml | 1 + src/rust/src/lib.rs | 1 + 7 files changed, 414 insertions(+), 109 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dee27f1f816e..0a4eab56571e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -257,7 +257,11 @@ install(TARGETS http_parser EXPORT ccf DESTINATION lib) add_ccf_static_library( ccf_pal SRCS ${CCF_DIR}/src/pal/attestation.cpp - LINK_LIBS ccfcrypto + LINK_LIBS ccfcrypto ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS} m +) +target_include_directories( + ccf_pal + PRIVATE ${CCF_DIR}/3rdparty/internal/tee-attestation-verification/ffi/include ) # CCF js lib diff --git a/cmake/ccf_rs.cmake b/cmake/ccf_rs.cmake index d24e557d8c0e..3dbac6ed9c67 100644 --- a/cmake/ccf_rs.cmake +++ b/cmake/ccf_rs.cmake @@ -75,6 +75,7 @@ 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 diff --git a/src/pal/attestation.cpp b/src/pal/attestation.cpp index 8df0a1fbea34..38e36780490a 100644 --- a/src/pal/attestation.cpp +++ b/src/pal/attestation.cpp @@ -3,15 +3,15 @@ #include "ccf/pal/attestation.h" -#include "ccf/crypto/ecdsa.h" #include "ccf/crypto/openssl/openssl_wrappers.h" -#include "ccf/crypto/verifier.h" #include "ccf/ds/json.h" #include "ccf/pal/attestation_sev_snp.h" #include "ccf/pal/sev_snp_cpuid.h" #include "ds/internal_logger.h" +#include "tav/snp.h" #include +#include #include namespace ccf::pal @@ -21,32 +21,6 @@ namespace ccf::pal using Unique_ASN1_INTEGER = ccf::crypto::OpenSSL:: Unique_SSL_OBJECT; - namespace - { - std::string x509_name_to_rfc2253_string(X509_NAME* name) - { - ccf::crypto::OpenSSL::CHECKNULL(name); - - ccf::crypto::OpenSSL::Unique_BIO mem; - const auto rc = X509_NAME_print_ex(mem, name, 0, XN_FLAG_RFC2253); - if (rc < 0) - { - const auto ec = ERR_get_error(); - throw std::runtime_error(fmt::format( - "OpenSSL error (rc={}, ec={}): {}", - rc, - ec, - ccf::crypto::OpenSSL::error_string(ec))); - } - - BUF_MEM* bptr = nullptr; - ccf::crypto::OpenSSL::CHECK1(BIO_get_mem_ptr(mem, &bptr)); - ccf::crypto::OpenSSL::CHECKNULL(bptr); - - return {bptr->data, bptr->length}; - } - } - void verify_virtual_attestation_report( const QuoteInfo& quote_info, PlatformAttestationMeasurement& measurement, @@ -286,84 +260,39 @@ namespace ccf::pal auto ask_cert = certificates[1]; auto ark_cert = certificates[2]; - auto ark_verifier = ccf::crypto::make_verifier(ark_cert); - - auto key = snp::amd_root_signing_keys.find(product_family); - if (key == snp::amd_root_signing_keys.end()) - { - throw std::logic_error(fmt::format( - "SEV-SNP: No known root certificate for {}", product_family)); - } - const auto& expected_ark = key->second; - if (ark_verifier->public_key_pem().str() != expected_ark.public_key) - { - throw std::logic_error(fmt::format( - "SEV-SNP: The root of trust public key for this attestation was not " - "the expected one for v{} {} {}: {} != {}", - quote.version, - quote.cpuid_fam_id, - quote.cpuid_mod_id, - ark_verifier->public_key_pem().str(), - expected_ark.public_key)); - } - - ccf::crypto::OpenSSL::Unique_BIO mem_bio(ark_cert); - ccf::crypto::OpenSSL::Unique_X509 x509( - mem_bio, true, true /* check_null */); - const auto issuer = x509_name_to_rfc2253_string(X509_get_issuer_name(x509)); - if (issuer != expected_ark.issuer) - { - throw std::logic_error(fmt::format( - "SEV-SNP: The root of trust issuer for this attestation was not " - "the expected one for {}: {} != {}", - product_family, - issuer, - expected_ark.issuer)); - } - - if (!ark_verifier->verify_certificate({&ark_cert})) - { - throw std::logic_error( - "SEV-SNP: The root of trust public key for this attestation was not " - "self signed as expected"); - } - - auto vcek_verifier = ccf::crypto::make_verifier(/* leaf */ vcek_cert); - if (!vcek_verifier->verify_certificate( - /* root */ {&ark_cert}, /* chain */ {&ask_cert})) - { - throw std::logic_error( - "SEV-SNP: The chain of signatures from the root of trust to this " - "attestation is broken"); - } - - // ---- Verify attestation report signature ---- - - // According to Table 134 (2025-06-12) only ecdsa_p384_sha384 is supported - if (quote.signature_algo != snp::SignatureAlgorithm::ecdsa_p384_sha384) + TavSnpAttestationReport* verified_report_raw = nullptr; + using TavErrorPtr = std::unique_ptr; + TavErrorPtr verification_error( + tav_verify_snp_attestation( + quote_info.quote.data(), + quote_info.quote.size(), + ark_cert.data(), + ark_cert.size(), + ask_cert.data(), + ask_cert.size(), + vcek_cert.data(), + vcek_cert.size(), + &verified_report_raw), + tav_error_free); + if (verification_error != nullptr) { + const auto error_code = tav_error_code(verification_error.get()); + const auto* error_message = tav_error_message(verification_error.get()); throw std::logic_error(fmt::format( - "SEV-SNP: Unsupported signature algorithm: {} (supported: {})", - quote.signature_algo, - snp::SignatureAlgorithm::ecdsa_p384_sha384)); + "SEV-SNP: TAV verification failed ({}): {}", + static_cast(error_code), + error_message == nullptr ? "Unknown TAV error" : error_message)); } - // Make ASN1 DER signature - auto quote_signature = ccf::crypto::ecdsa_sig_from_r_s( - quote.signature.r, - sizeof(quote.signature.r), - quote.signature.s, - sizeof(quote.signature.s), - false /* little endian */ - ); - - std::span quote_without_signature{ - quote_info.quote.data(), - quote_info.quote.size() - sizeof(quote.signature)}; - if (!vcek_verifier->verify(quote_without_signature, quote_signature)) + using TavReportPtr = std::unique_ptr< + TavSnpAttestationReport, + decltype(&tav_snp_attestation_report_free)>; + TavReportPtr verified_report( + verified_report_raw, tav_snp_attestation_report_free); + if (verified_report == nullptr) { throw std::logic_error( - "SEV-SNP: Chip certificate (VCEK) did not sign this attestation"); + "SEV-SNP: TAV verification succeeded without returning a report"); } // ---- Verify attestation report contents ---- diff --git a/src/pal/test/snp_attestation_validation.cpp b/src/pal/test/snp_attestation_validation.cpp index afc8d026418e..4d64509b4217 100644 --- a/src/pal/test/snp_attestation_validation.cpp +++ b/src/pal/test/snp_attestation_validation.cpp @@ -188,6 +188,30 @@ TEST_CASE("turin validation") turin_quote_info, measurement, report_data); } +TEST_CASE("Invalid attestation signature fails TAV verification") +{ + using namespace ccf; + + auto invalid_attestation = pal::snp::testing::milan_attestation; + invalid_attestation[offsetof(pal::snp::Attestation, signature)] ^= 1; + auto quote_info = QuoteInfo{ + .format = QuoteFormat::amd_sev_snp_v1, + .quote = std::move(invalid_attestation), + .endorsements = std::vector( + pal::snp::testing::milan_endorsements.begin(), + pal::snp::testing::milan_endorsements.end()), + .uvm_endorsements = std::nullopt, + }; + + pal::PlatformAttestationMeasurement measurement; + pal::PlatformAttestationReportData report_data; + + CHECK_THROWS_WITH_AS( + pal::verify_snp_attestation_report(quote_info, measurement, report_data), + doctest::Contains("SEV-SNP: TAV verification failed (104):"), + std::logic_error); +} + TEST_CASE("Mismatched attestation and endorsements fail") { using namespace ccf; @@ -207,9 +231,7 @@ TEST_CASE("Mismatched attestation and endorsements fail") CHECK_THROWS_WITH_AS( pal::verify_snp_attestation_report( mismatched_quote, measurement, report_data), - doctest::Contains( - "SEV-SNP: The root of trust public key for this attestation " - "was not the expected one"), + doctest::Contains("SEV-SNP: TAV verification failed (102):"), std::logic_error); } @@ -223,9 +245,7 @@ TEST_CASE("ARK with unexpected issuer fails") CHECK_THROWS_WITH_AS( ccf::pal::verify_snp_attestation_report( quote_info, measurement, report_data), - doctest::Contains( - "SEV-SNP: The root of trust issuer for this attestation was not " - "the expected one"), + doctest::Contains("SEV-SNP: TAV verification failed (102):"), std::logic_error); } diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index c11b3e2a122c..68fd8bbc18f3 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -2,6 +2,18 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + [[package]] name = "cborrs" version = "0.1.0" @@ -27,8 +39,15 @@ name = "ccf-rs" version = "0.1.0" dependencies = [ "cose-rs", + "tee-attestation-verification-ffi", ] +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + [[package]] name = "cose-openssl" version = "0.1.0" @@ -51,17 +70,116 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + [[package]] name = "libc" version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "openssl" +version = "0.10.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "openssl-sys" -version = "0.9.112" +version = "0.9.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" dependencies = [ "cc", "libc", @@ -69,20 +187,251 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + [[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tee-attestation-verification-caci" +version = "1.0.3" +dependencies = [ + "serde_json", + "tee-attestation-verification-cose", + "tee-attestation-verification-crypto", + "tee-attestation-verification-lib", +] + +[[package]] +name = "tee-attestation-verification-cose" +version = "1.0.3" +dependencies = [ + "cborrs", + "cborrs-nondet", + "tee-attestation-verification-crypto", +] + +[[package]] +name = "tee-attestation-verification-crypto" +version = "1.0.3" +dependencies = [ + "foreign-types", + "js-sys", + "openssl", + "openssl-sys", + "wasm-bindgen", + "wasm-bindgen-futures", +] + +[[package]] +name = "tee-attestation-verification-ffi" +version = "1.0.3" +dependencies = [ + "js-sys", + "serde_json", + "tee-attestation-verification-caci", + "tee-attestation-verification-cose", + "tee-attestation-verification-crypto", + "tee-attestation-verification-lib", + "wasm-bindgen", + "wasm-bindgen-futures", + "zerocopy", +] + +[[package]] +name = "tee-attestation-verification-lib" +version = "1.0.3" +dependencies = [ + "log", + "tee-attestation-verification-crypto", + "zerocopy", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "zerocopy" +version = "0.8.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 06c5c8d89a51..e708af4adb2f 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -8,6 +8,7 @@ crate-type = ["staticlib"] [dependencies] cose-rs = { path = "../cose/cose_rs" } +tav = { package = "tee-attestation-verification-ffi", path = "../../3rdparty/internal/tee-attestation-verification/ffi", default-features = false, features = ["crypto_openssl"] } [profile.release] lto = true diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs index 83a1476d5c74..49e6c84ed70b 100644 --- a/src/rust/src/lib.rs +++ b/src/rust/src/lib.rs @@ -2,3 +2,4 @@ // Licensed under the Apache 2.0 License. pub use cose_rs; +pub use tav; From df214311ea796f03331330aec0c139404869e6a8 Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Wed, 5 Aug 2026 17:32:59 +0100 Subject: [PATCH 02/14] add wrapper --- src/pal/attestation.cpp | 32 ++++++++++++-------------------- src/pal/tav_ffi.h | 28 ++++++++++++++++++++++++++++ src/rust/Cargo.lock | 10 +++++----- 3 files changed, 45 insertions(+), 25 deletions(-) create mode 100644 src/pal/tav_ffi.h diff --git a/src/pal/attestation.cpp b/src/pal/attestation.cpp index 38e36780490a..3edd7268716b 100644 --- a/src/pal/attestation.cpp +++ b/src/pal/attestation.cpp @@ -8,10 +8,9 @@ #include "ccf/pal/attestation_sev_snp.h" #include "ccf/pal/sev_snp_cpuid.h" #include "ds/internal_logger.h" -#include "tav/snp.h" +#include "pal/tav_ffi.h" #include -#include #include namespace ccf::pal @@ -261,19 +260,17 @@ namespace ccf::pal auto ark_cert = certificates[2]; TavSnpAttestationReport* verified_report_raw = nullptr; - using TavErrorPtr = std::unique_ptr; - TavErrorPtr verification_error( - tav_verify_snp_attestation( - quote_info.quote.data(), - quote_info.quote.size(), - ark_cert.data(), - ark_cert.size(), - ask_cert.data(), - ask_cert.size(), - vcek_cert.data(), - vcek_cert.size(), - &verified_report_raw), - tav_error_free); + TavErrorPtr verification_error(tav_verify_snp_attestation( + quote_info.quote.data(), + quote_info.quote.size(), + ark_cert.data(), + ark_cert.size(), + ask_cert.data(), + ask_cert.size(), + vcek_cert.data(), + vcek_cert.size(), + &verified_report_raw)); + TavAttestationReportPtr verified_report(verified_report_raw); if (verification_error != nullptr) { const auto error_code = tav_error_code(verification_error.get()); @@ -284,11 +281,6 @@ namespace ccf::pal error_message == nullptr ? "Unknown TAV error" : error_message)); } - using TavReportPtr = std::unique_ptr< - TavSnpAttestationReport, - decltype(&tav_snp_attestation_report_free)>; - TavReportPtr verified_report( - verified_report_raw, tav_snp_attestation_report_free); if (verified_report == nullptr) { throw std::logic_error( diff --git a/src/pal/tav_ffi.h b/src/pal/tav_ffi.h new file mode 100644 index 000000000000..1ff00bf70c5f --- /dev/null +++ b/src/pal/tav_ffi.h @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the Apache 2.0 License. + +#pragma once + +#include "tav/snp.h" +#include "tav/utils.h" + +#include + +namespace ccf::pal +{ + template + struct TavDeleter + { + void operator()(T* ptr) const noexcept + { + Free(ptr); + } + }; + + template + using TavUniquePtr = std::unique_ptr>; + + using TavErrorPtr = TavUniquePtr; + using TavAttestationReportPtr = + TavUniquePtr; +} diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 68fd8bbc18f3..f106a17b052b 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -290,7 +290,7 @@ dependencies = [ [[package]] name = "tee-attestation-verification-caci" -version = "1.0.3" +version = "1.0.4" dependencies = [ "serde_json", "tee-attestation-verification-cose", @@ -300,7 +300,7 @@ dependencies = [ [[package]] name = "tee-attestation-verification-cose" -version = "1.0.3" +version = "1.0.4" dependencies = [ "cborrs", "cborrs-nondet", @@ -309,7 +309,7 @@ dependencies = [ [[package]] name = "tee-attestation-verification-crypto" -version = "1.0.3" +version = "1.0.4" dependencies = [ "foreign-types", "js-sys", @@ -321,7 +321,7 @@ dependencies = [ [[package]] name = "tee-attestation-verification-ffi" -version = "1.0.3" +version = "1.0.4" dependencies = [ "js-sys", "serde_json", @@ -336,7 +336,7 @@ dependencies = [ [[package]] name = "tee-attestation-verification-lib" -version = "1.0.3" +version = "1.0.4" dependencies = [ "log", "tee-attestation-verification-crypto", From 4376aa14a824e428166da6dfe22c00314ef69600 Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Thu, 6 Aug 2026 18:04:44 +0100 Subject: [PATCH 03/14] Remove remainder of snp --- CHANGELOG.md | 1 + CMakeLists.txt | 1 + include/ccf/node/quote.h | 2 +- include/ccf/pal/attestation.h | 6 + include/ccf/pal/attestation_sev_snp.h | 288 +++++------------ include/ccf/pal/snp_ioctl6.h | 19 +- src/js/extensions/snp_attestation.cpp | 94 +++--- src/node/node_state.h | 22 +- src/node/quote.cpp | 33 +- src/pal/attestation.cpp | 305 +++++++++++++++--- src/pal/quote_generation.h | 11 +- src/pal/test/snp_attestation_validation.cpp | 62 +++- src/pal/test/snp_ioctl_test.cpp | 5 +- src/pal/test/verify_attestation.cpp | 19 +- ...erify_uvm_attestation_and_endorsements.cpp | 7 +- src/rust/Cargo.lock | 10 +- src/service/internal_tables_access.h | 20 +- 17 files changed, 524 insertions(+), 381 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfd7ef5bcda6..80b14fcb9eb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,6 +87,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed +- SNP attestation reports are now parsed and verified through TAV accessors. The public packed `ccf::pal::snp::Attestation` wire-layout type has been replaced by the move-only `ccf::pal::snp::AttestationReport` accessor API. (#8083) - TLS handshakes now prefer hybrid post-quantum key exchange groups, in the order `SecP384r1MLKEM1024`, `SecP256r1MLKEM768`, `X25519MLKEM768`, when the linked crypto provider supports them. The `P-521`, `P-384` and `P-256` groups are retained as fallbacks (#8107). - `ccf.cose.verify_receipt()` has moved and been renamed to `ccf.receipt.verify_cose()`; the old name still works but is deprecated (#8109). diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a4eab56571e..495b51bf745d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -597,6 +597,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) diff --git a/include/ccf/node/quote.h b/include/ccf/node/quote.h index ac9e983ab0ce..182c010c4349 100644 --- a/include/ccf/node/quote.h +++ b/include/ccf/node/quote.h @@ -39,7 +39,7 @@ namespace ccf static std::optional get_host_data(const QuoteInfo& quote_info); - static std::optional get_snp_attestation( + static std::optional get_snp_attestation( const QuoteInfo& quote_info); static QuoteVerificationResult verify_quote_against_store( diff --git a/include/ccf/pal/attestation.h b/include/ccf/pal/attestation.h index 19fc6ed55ab1..f5aa722f709e 100644 --- a/include/ccf/pal/attestation.h +++ b/include/ccf/pal/attestation.h @@ -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" @@ -28,6 +29,11 @@ namespace ccf::pal PlatformAttestationMeasurement& measurement, PlatformAttestationReportData& report_data); + 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, diff --git a/include/ccf/pal/attestation_sev_snp.h b/include/ccf/pal/attestation_sev_snp.h index 600ed237a403..f64c554bbd39 100644 --- a/include/ccf/pal/attestation_sev_snp.h +++ b/include/ccf/pal/attestation_sev_snp.h @@ -14,8 +14,9 @@ #include #include #include -#include +#include #include +#include #include #include #include @@ -27,77 +28,6 @@ namespace ccf::pal::snp static constexpr auto NO_SECURITY_POLICY = ""; - // From https://developer.amd.com/sev/ - constexpr auto amd_milan_root_signing_public_key = - R"(-----BEGIN PUBLIC KEY----- -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0Ld52RJOdeiJlqK2JdsV -mD7FktuotWwX1fNgW41XY9Xz1HEhSUmhLz9Cu9DHRlvgJSNxbeYYsnJfvyjx1MfU -0V5tkKiU1EesNFta1kTA0szNisdYc9isqk7mXT5+KfGRbfc4V/9zRIcE8jlHN61S -1ju8X93+6dxDUrG2SzxqJ4BhqyYmUDruPXJSX4vUc01P7j98MpqOS95rORdGHeI5 -2Naz5m2B+O+vjsC060d37jY9LFeuOP4Meri8qgfi2S5kKqg/aF6aPtuAZQVR7u3K -FYXP59XmJgtcog05gmI0T/OitLhuzVvpZcLph0odh/1IPXqx3+MnjD97A7fXpqGd -/y8KxX7jksTEzAOgbKAeam3lm+3yKIcTYMlsRMXPcjNbIvmsBykD//xSniusuHBk -gnlENEWx1UcbQQrs+gVDkuVPhsnzIRNgYvM48Y+7LGiJYnrmE8xcrexekBxrva2V -9TJQqnN3Q53kt5viQi3+gCfmkwC0F0tirIZbLkXPrPwzZ0M9eNxhIySb2npJfgnq -z55I0u33wh4r0ZNQeTGfw03MBUtyuzGesGkcw+loqMaq1qR4tjGbPYxCvpCq7+Og -pCCoMNit2uLo9M18fHz10lOMT8nWAUvRZFzteXCm+7PHdYPlmQwUw3LvenJ/ILXo -QPHfbkH0CyPfhl1jWhJFZasCAwEAAQ== ------END PUBLIC KEY----- -)"; - constexpr auto amd_genoa_root_signing_public_key = - R"(-----BEGIN PUBLIC KEY----- -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA3Cd95S/uFOuRIskW9vz9 -VDBF69NDQF79oRhL/L2PVQGhK3YdfEBgpF/JiwWFBsT/fXDhzA01p3LkcT/7Ldjc -RfKXjHl+0Qq/M4dZkh6QDoUeKzNBLDcBKDDGWo3v35NyrxbA1DnkYwUKU5AAk4P9 -4tKXLp80oxt84ahyHoLmc/LqsGsp+oq1Bz4PPsYLwTG4iMKVaaT90/oZ4I8oibSr -u92vJhlqWO27d/Rxc3iUMyhNeGToOvgx/iUo4gGpG61NDpkEUvIzuKcaMx8IdTpW -g2DF6SwF0IgVMffnvtJmA68BwJNWo1E4PLJdaPfBifcJpuBFwNVQIPQEVX3aP89H -JSp8YbY9lySS6PlVEqTBBtaQmi4ATGmMR+n2K/e+JAhU2Gj7jIpJhOkdH9firQDn -mlA2SFfJ/Cc0mGNzW9RmIhyOUnNFoclmkRhl3/AQU5Ys9Qsan1jT/EiyT+pCpmnA -+y9edvhDCbOG8F2oxHGRdTBkylungrkXJGYiwGrR8kaiqv7NN8QhOBMqYjcbrkEr -0f8QMKklIS5ruOfqlLMCBw8JLB3LkjpWgtD7OpxkzSsohN47Uom86RY6lp72g8eX -HP1qYrnvhzaG1S70vw6OkbaaC9EjiH/uHgAJQGxon7u0Q7xgoREWA/e7JcBQwLg8 -0Hq/sbRuqesxz7wBWSY254cCAwEAAQ== ------END PUBLIC KEY----- -)"; - constexpr auto amd_turin_root_signing_public_key = - R"(-----BEGIN PUBLIC KEY----- -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwaAriB7EIuVc4ZB1wD3Y -fDxL+9eyS7+izm0Jj3W772NINCWl8Bj3w/JD2ZjmbRxWdIq/4d9iarCKorXloJUB -1jRdgxqccTx1aOoig4+2w1XhVVJT7K457wT5ZLNJgQaxqa9Etkwjd6+9sOhlCDE9 -l43kQ0R2BikVJa/uyyVOSwEk5w5tXKOuG9jvq6QtAMJasW38wlqRDaKEGtZ9VUgG -on27ZuL4sTJuC/azz9/iQBw8kEilzOl95AiTkeY5jSEBDWbAqnZk5qlM7kISKG20 -kgQm14mhNKDI2p2oua+zuAG7i52epoRF2GfU0TYk/yf+vCNB2tnechFQuP2e8bLk -95ZdqPi9/UWw4JXjtdEA4u2JYplSSUPQVAXKt6LVqujtJcM59JKr2u0XQ75KwxcM -p15gSXhBfInvPAwuAY4dEwwGqT8oIg4esPHwEsmChhYeDIxPG9R4fx9O0q6p8Gb+ -HXlTiS47P9YNeOpidOUKzDl/S1OvyhDtSL8LJc24QATFydo/iD/KUdvFTRlD0crk -AMkZLoWQ8hLDGc6BZJXsdd7Zf2e4UW3tI/1oh/2t23Ot3zyhTcv5gDbABu0LjVe9 -8uRnS15SMwK//lJt9e5BqKvgABkSoABf+B4VFtPVEX0ygrYaFaI9i5ABrxnVBmzX -pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== ------END PUBLIC KEY----- -)"; - - struct AmdRootSigningKey - { - const char* public_key; - const char* issuer; - }; - - inline const std::map amd_root_signing_keys{ - {ProductName::Milan, - {amd_milan_root_signing_public_key, - "CN=ARK-Milan,O=Advanced Micro Devices,ST=CA,L=Santa Clara,C=US," - "OU=Engineering"}}, - {ProductName::Genoa, - {amd_genoa_root_signing_public_key, - "CN=ARK-Genoa,O=Advanced Micro Devices,ST=CA,L=Santa Clara,C=US," - "OU=Engineering"}}, - {ProductName::Turin, - {amd_turin_root_signing_public_key, - "CN=ARK-Turin,O=Advanced Micro Devices,ST=CA,L=Santa Clara,C=US," - "OU=Engineering"}}, - }; - #pragma pack(push, 1) // Table 3 constexpr size_t snp_tcb_version_size = 8; @@ -311,7 +241,74 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== static_assert( sizeof(TcbVersionRaw) == snp_tcb_version_size, "TCB version raw size mismatch"); -#pragma pack(push, 1) + + class AttestationReportFactory; + + class AttestationReport + { + private: + class Impl; + std::unique_ptr impl; + + explicit AttestationReport(std::unique_ptr impl_); + friend class AttestationReportFactory; + + public: + AttestationReport(AttestationReport&&) noexcept; + AttestationReport& operator=(AttestationReport&&) noexcept; + ~AttestationReport(); + + AttestationReport(const AttestationReport&) = delete; + AttestationReport& operator=(const AttestationReport&) = delete; + + [[nodiscard]] uint32_t version() const; + [[nodiscard]] uint32_t guest_svn() const; + [[nodiscard]] uint64_t policy() const; + [[nodiscard]] uint8_t policy_abi_minor() const; + [[nodiscard]] uint8_t policy_abi_major() const; + [[nodiscard]] bool policy_smt() const; + [[nodiscard]] bool policy_migrate_ma() const; + [[nodiscard]] bool policy_debug() const; + [[nodiscard]] bool policy_single_socket() const; + [[nodiscard]] uint32_t vmpl() const; + [[nodiscard]] uint32_t signature_algo() const; + [[nodiscard]] uint64_t platform_info() const; + [[nodiscard]] uint32_t flags() const; + [[nodiscard]] bool flags_author_key_en() const; + [[nodiscard]] bool flags_mask_chip_key() const; + [[nodiscard]] uint8_t flags_signing_key() const; + [[nodiscard]] uint8_t cpuid_fam_id() const; + [[nodiscard]] uint8_t cpuid_mod_id() const; + [[nodiscard]] uint8_t cpuid_step() const; + [[nodiscard]] uint8_t current_build() const; + [[nodiscard]] uint8_t current_minor() const; + [[nodiscard]] uint8_t current_major() const; + [[nodiscard]] uint8_t committed_build() const; + [[nodiscard]] uint8_t committed_minor() const; + [[nodiscard]] uint8_t committed_major() const; + + [[nodiscard]] std::vector family_id() const; + [[nodiscard]] std::vector image_id() const; + [[nodiscard]] TcbVersionRaw platform_version() const; + [[nodiscard]] std::vector report_data() const; + [[nodiscard]] std::vector measurement() const; + [[nodiscard]] std::vector host_data() const; + [[nodiscard]] std::vector id_key_digest() const; + [[nodiscard]] std::vector author_key_digest() const; + [[nodiscard]] std::vector report_id() const; + [[nodiscard]] std::vector report_id_ma() const; + [[nodiscard]] TcbVersionRaw reported_tcb() const; + [[nodiscard]] std::vector chip_id() const; + [[nodiscard]] std::vector chip_id_for_vcek() const; + [[nodiscard]] TcbVersionRaw committed_tcb() const; + [[nodiscard]] TcbVersionRaw launch_tcb() const; + [[nodiscard]] std::vector signature_r() const; + [[nodiscard]] std::vector signature_s() const; + }; + + AttestationReport parse_attestation_report_unverified( + std::span report); + inline void to_json(nlohmann::json& j, const TcbVersionRaw& tcb_version) { j = tcb_version.to_hex(); @@ -331,131 +328,11 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== return "TcbVersionRaw"; } - struct Signature - { - uint8_t r[72]; - uint8_t s[72]; - uint8_t reserved[512 - 144]; - }; -#pragma pack(pop) - - // Table 105 - // NOLINTNEXTLINE(performance-enum-size) - enum class SignatureAlgorithm : uint32_t - { - invalid = 0, - ecdsa_p384_sha384 = 1 - }; - -#pragma pack(push, 1) - // Table 8 - struct GuestPolicy - { - uint8_t abi_minor; - uint8_t abi_major; - uint8_t smt : 1; - uint8_t reserved : 1; - uint8_t migrate_ma : 1; - uint8_t debug : 1; - uint8_t single_socket : 1; - uint64_t reserved2 : 43; - }; -#pragma pack(pop) - static_assert( - sizeof(GuestPolicy) == sizeof(uint64_t), - "Cannot cast GuestPolicy to uint64_t"); - static constexpr uint8_t attestation_flags_signing_key_vcek = 0; - -#pragma pack(push, 1) - struct Flags - { - uint8_t author_key_en : 1; - uint8_t mask_chip_key : 1; - uint8_t signing_key : 3; - uint64_t reserved : 27; - }; -#pragma pack(pop) - static_assert( - sizeof(Flags) == sizeof(uint32_t), "Cannot cast Flags to uint32_t"); - -#pragma pack(push, 1) - // Table 22 - struct PlatformInfo - { - uint8_t smt_en : 1; - uint8_t tsme_en : 1; - uint64_t reserved : 62; - }; -#pragma pack(pop) - static_assert( - sizeof(PlatformInfo) == sizeof(uint64_t), - "Cannot cast PlatformInfo to uint64_t"); - -#pragma pack(push, 1) - // Table 21 - + static constexpr size_t attestation_report_size = 1184; static constexpr uint32_t minimum_attestation_version = 3; static constexpr uint32_t attestation_policy_abi_major = 1; - struct Attestation - { - uint32_t version = 0; /* 0x000 */ - uint32_t guest_svn = 0; /* 0x004 */ - struct GuestPolicy policy = {}; /* 0x008 */ - uint8_t family_id[16] = {0}; /* 0x010 */ - uint8_t image_id[16] = {0}; /* 0x020 */ - uint32_t vmpl = 0; /* 0x030 */ - SignatureAlgorithm signature_algo = {}; /* 0x034 */ - TcbVersionRaw platform_version; /* 0x038 */ - PlatformInfo platform_info = {}; /* 0x040 */ - Flags flags = {}; /* 0x048 */ - uint32_t reserved0 = 0; /* 0x04C */ - uint8_t report_data[snp_attestation_report_data_size] = {0}; /* 0x050 */ - uint8_t measurement[snp_attestation_measurement_size] = {0}; /* 0x090 */ - uint8_t host_data[32] = {0}; /* 0x0C0 */ - uint8_t id_key_digest[48] = {0}; /* 0x0E0 */ - uint8_t author_key_digest[48] = {0}; /* 0x110 */ - uint8_t report_id[32] = {0}; /* 0x140 */ - uint8_t report_id_ma[32] = {0}; /* 0x160 */ - TcbVersionRaw reported_tcb; /* 0x180 */ - uint8_t cpuid_fam_id = 0; /* 0x188*/ - uint8_t cpuid_mod_id = 0; /* 0x189 */ - uint8_t cpuid_step = 0; /* 0x18A */ - 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_major = 0; /* 0x1EA */ - uint8_t reserved2 = 0; /* 0x1EB */ - uint8_t committed_build = 0; /* 0x1EC */ - uint8_t committed_minor = 0; /* 0x1ED */ - uint8_t committed_major = 0; /* 0x1EE */ - uint8_t reserved3 = 0; /* 0x1EF */ - TcbVersionRaw launch_tcb; /* 0x1F0 */ - uint8_t reserved4[168] = {0}; /* 0x1F8 */ - struct Signature signature = {}; /* 0x2A0 */ - - [[nodiscard]] std::span 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) - static HostPort get_endpoint_loc( const EndorsementsServer& server, const HostPort& default_values) { @@ -475,24 +352,27 @@ 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.version() < minimum_attestation_version) { throw std::logic_error(fmt::format( "SEV-SNP: attestation version {} is not supported. Minimum " "supported version is {}", - quote.version, + quote.version(), minimum_attestation_version)); } EndorsementEndpointsConfiguration config; auto chip_id_hex = - fmt::format("{:02x}", fmt::join(quote.get_chip_id_for_vcek(), "")); - auto reported_tcb = fmt::format( - "{:0x}", *reinterpret_cast("e.reported_tcb)); + fmt::format("{:02x}", fmt::join(quote.chip_id_for_vcek(), "")); + const auto reported_tcb_raw = quote.reported_tcb().data(); + uint64_t reported_tcb_value = 0; + std::memcpy( + &reported_tcb_value, reported_tcb_raw.data(), sizeof(reported_tcb_value)); + auto reported_tcb = fmt::format("{:0x}", reported_tcb_value); constexpr size_t default_max_retries_count = 10; static const ds::SizeString default_max_client_response_size = @@ -534,7 +414,7 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== case EndorsementsEndpointType::AMD: { auto product = - get_sev_snp_product(quote.cpuid_fam_id, quote.cpuid_mod_id); + get_sev_snp_product(quote.cpuid_fam_id(), quote.cpuid_mod_id()); std::string boot_loader; std::string tee; @@ -546,7 +426,8 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== case ProductName::Milan: case ProductName::Genoa: { - auto tcb = quote.reported_tcb.to_policy(product).to_milan_genoa(); + auto tcb = + quote.reported_tcb().to_policy(product).to_milan_genoa(); boot_loader = fmt::format("{}", tcb.boot_loader); tee = fmt::format("{}", tcb.tee); snp = fmt::format("{}", tcb.snp); @@ -555,7 +436,7 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== } case ProductName::Turin: { - auto tcb = quote.reported_tcb.to_policy(product).to_turin(); + auto tcb = quote.reported_tcb().to_policy(product).to_turin(); boot_loader = fmt::format("{}", tcb.boot_loader); tee = fmt::format("{}", tcb.tee); snp = fmt::format("{}", tcb.snp); @@ -611,7 +492,6 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== class AttestationInterface { public: - [[nodiscard]] virtual const snp::Attestation& get() const = 0; virtual std::vector get_raw() = 0; virtual ~AttestationInterface() = default; diff --git a/include/ccf/pal/snp_ioctl6.h b/include/ccf/pal/snp_ioctl6.h index 344febe028d4..5814c6a94e02 100644 --- a/include/ccf/pal/snp_ioctl6.h +++ b/include/ccf/pal/snp_ioctl6.h @@ -116,11 +116,13 @@ namespace ccf::pal::snp::ioctl6 uint32_t status = 0; uint32_t report_size = 0; uint8_t reserved[0x20 - 0x8] = {0}; - Attestation report; + std::array report = {}; uint8_t padding[64] = {0}; // padding to the size of SEV_SNP_REPORT_RSP_BUF_SZ (i.e., 1280 bytes) }; #pragma pack(pop) + static_assert(offsetof(AttestationResp, report) == 0x20); + static_assert(sizeof(AttestationResp) == 1280); // Table 20 of the SEVSNP ABI constexpr uint8_t GUEST_FIELD_SELECT_GUEST_POLICY = 0b00000001; @@ -260,15 +262,16 @@ namespace ccf::pal::snp::ioctl6 } } - [[nodiscard]] const snp::Attestation& get() const override - { - return padded_resp.report; - } - std::vector get_raw() override { - auto* quote_bytes = reinterpret_cast(&padded_resp.report); - return {quote_bytes, quote_bytes + padded_resp.report_size}; + if (padded_resp.report_size != attestation_report_size) + { + throw std::logic_error(fmt::format( + "Unexpected SEV-SNP attestation report size: {} != {}", + padded_resp.report_size, + attestation_report_size)); + } + return {padded_resp.report.begin(), padded_resp.report.end()}; } }; diff --git a/src/js/extensions/snp_attestation.cpp b/src/js/extensions/snp_attestation.cpp index 1b72e4d50f4f..14dc8f623b1f 100644 --- a/src/js/extensions/snp_attestation.cpp +++ b/src/js/extensions/snp_attestation.cpp @@ -99,11 +99,12 @@ namespace ccf::js::extensions pal::PlatformAttestationMeasurement measurement = {}; pal::PlatformAttestationReportData report_data = {}; std::optional parsed_uvm_endorsements; + std::optional verified_attestation; try { - pal::verify_snp_attestation_report( - quote_info, measurement, report_data); + verified_attestation.emplace(pal::verify_snp_attestation_report_and_get( + quote_info, measurement, report_data)); if (uvm_endorsements.has_value()) { parsed_uvm_endorsements = @@ -118,8 +119,7 @@ namespace ccf::js::extensions return JS_ThrowRangeError(ctx, "%s", e.what()); } - auto attestation = *reinterpret_cast( - quote_info.quote.data()); + const auto& attestation = verified_attestation.value(); auto r = jsctx.new_obj(); JS_CHECK_EXC(r); @@ -127,44 +127,44 @@ namespace ccf::js::extensions auto a = jsctx.new_obj(); JS_CHECK_EXC(a); - JS_CHECK_SET(a.set_uint32("version", attestation.version)); - JS_CHECK_SET(a.set_uint32("guest_svn", attestation.guest_svn)); + JS_CHECK_SET(a.set_uint32("version", attestation.version())); + JS_CHECK_SET(a.set_uint32("guest_svn", attestation.guest_svn())); auto policy = jsctx.new_obj(); JS_CHECK_EXC(policy); JS_CHECK_SET( - policy.set_uint32("abi_minor", attestation.policy.abi_minor)); + policy.set_uint32("abi_minor", attestation.policy_abi_minor())); JS_CHECK_SET( - policy.set_uint32("abi_major", attestation.policy.abi_major)); - JS_CHECK_SET(policy.set_uint32("smt", attestation.policy.smt)); + policy.set_uint32("abi_major", attestation.policy_abi_major())); + JS_CHECK_SET(policy.set_uint32("smt", attestation.policy_smt())); JS_CHECK_SET( - policy.set_uint32("migrate_ma", attestation.policy.migrate_ma)); - JS_CHECK_SET(policy.set_uint32("debug", attestation.policy.debug)); + policy.set_uint32("migrate_ma", attestation.policy_migrate_ma())); + JS_CHECK_SET(policy.set_uint32("debug", attestation.policy_debug())); JS_CHECK_SET( - policy.set_uint32("single_socket", attestation.policy.single_socket)); + policy.set_uint32("single_socket", attestation.policy_single_socket())); JS_CHECK_SET(a.set("policy", std::move(policy))); { - auto family_id = jsctx.new_array_buffer_copy(attestation.family_id); + auto family_id = jsctx.new_array_buffer_copy(attestation.family_id()); JS_CHECK_EXC(family_id); JS_CHECK_SET(a.set("family_id", std::move(family_id))); } { - auto image_id = jsctx.new_array_buffer_copy(attestation.image_id); + auto image_id = jsctx.new_array_buffer_copy(attestation.image_id()); JS_CHECK_EXC(image_id); JS_CHECK_SET(a.set("image_id", std::move(image_id))); } - JS_CHECK_SET(a.set_uint32("vmpl", attestation.vmpl)); + JS_CHECK_SET(a.set_uint32("vmpl", attestation.vmpl())); JS_CHECK_SET(a.set_uint32( - "signature_algo", static_cast(attestation.signature_algo))); + "signature_algo", static_cast(attestation.signature_algo()))); { - auto platform_version = - jsctx.wrap(make_js_tcb_version(jsctx, attestation.platform_version)); + auto platform_version = jsctx.wrap( + make_js_tcb_version(jsctx, attestation.platform_version())); JS_CHECK_EXC(platform_version); JS_CHECK_SET(a.set("platform_version", std::move(platform_version))); } @@ -172,10 +172,10 @@ namespace ccf::js::extensions { auto platform_info = jsctx.new_obj(); JS_CHECK_EXC(platform_info); + const auto raw_platform_info = attestation.platform_info(); + JS_CHECK_SET(platform_info.set_uint32("smt_en", raw_platform_info & 1)); JS_CHECK_SET( - platform_info.set_uint32("smt_en", attestation.platform_info.smt_en)); - JS_CHECK_SET(platform_info.set_uint32( - "tsme_en", attestation.platform_info.tsme_en)); + platform_info.set_uint32("tsme_en", (raw_platform_info >> 1) & 1)); JS_CHECK_SET(a.set("plaform_info", std::move(platform_info))); } @@ -183,38 +183,38 @@ namespace ccf::js::extensions auto flags = jsctx.new_obj(); JS_CHECK_EXC(flags); JS_CHECK_SET( - flags.set_uint32("author_key_en", attestation.flags.author_key_en)); + flags.set_uint32("author_key_en", attestation.flags_author_key_en())); JS_CHECK_SET( - flags.set_uint32("mask_chip_key", attestation.flags.mask_chip_key)); + flags.set_uint32("mask_chip_key", attestation.flags_mask_chip_key())); JS_CHECK_SET( - flags.set_uint32("signing_key", attestation.flags.signing_key)); + flags.set_uint32("signing_key", attestation.flags_signing_key())); JS_CHECK_SET(a.set("flags", std::move(flags))); } { auto attestation_report_data = - jsctx.new_array_buffer_copy(attestation.report_data); + jsctx.new_array_buffer_copy(attestation.report_data()); JS_CHECK_EXC(attestation_report_data); JS_CHECK_SET(a.set("report_data", std::move(attestation_report_data))); } { auto attestation_measurement = - jsctx.new_array_buffer_copy(attestation.measurement); + jsctx.new_array_buffer_copy(attestation.measurement()); JS_CHECK_EXC(attestation_measurement); JS_CHECK_SET(a.set("measurement", std::move(attestation_measurement))); } { auto attestation_host_data = - jsctx.new_array_buffer_copy(attestation.host_data); + jsctx.new_array_buffer_copy(attestation.host_data()); JS_CHECK_EXC(attestation_host_data); JS_CHECK_SET(a.set("host_data", std::move(attestation_host_data))); } { auto attestation_id_key_digest = - jsctx.new_array_buffer_copy(attestation.id_key_digest); + jsctx.new_array_buffer_copy(attestation.id_key_digest()); JS_CHECK_EXC(attestation_id_key_digest); JS_CHECK_SET( a.set("id_key_digest", std::move(attestation_id_key_digest))); @@ -222,7 +222,7 @@ namespace ccf::js::extensions { auto attestation_author_key_digest = - jsctx.new_array_buffer_copy(attestation.author_key_digest); + jsctx.new_array_buffer_copy(attestation.author_key_digest()); JS_CHECK_EXC(attestation_author_key_digest); JS_CHECK_SET( a.set("author_key_digest", std::move(attestation_author_key_digest))); @@ -230,14 +230,14 @@ namespace ccf::js::extensions { auto attestation_report_id = - jsctx.new_array_buffer_copy(attestation.report_id); + jsctx.new_array_buffer_copy(attestation.report_id()); JS_CHECK_EXC(attestation_report_id); JS_CHECK_SET(a.set("report_id", std::move(attestation_report_id))); } { auto attestation_report_id_ma = - jsctx.new_array_buffer_copy(attestation.report_id_ma); + jsctx.new_array_buffer_copy(attestation.report_id_ma()); JS_CHECK_EXC(attestation_report_id_ma); JS_CHECK_SET( a.set("report_id_ma", std::move(attestation_report_id_ma))); @@ -245,42 +245,42 @@ namespace ccf::js::extensions { auto reported_tcb = - jsctx.wrap(make_js_tcb_version(jsctx, attestation.reported_tcb)); + jsctx.wrap(make_js_tcb_version(jsctx, attestation.reported_tcb())); 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)); + 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); + 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))); } { auto committed_tcb = - jsctx.wrap(make_js_tcb_version(jsctx, attestation.committed_tcb)); + jsctx.wrap(make_js_tcb_version(jsctx, attestation.committed_tcb())); JS_CHECK_EXC(committed_tcb); JS_CHECK_SET(a.set("committed_tcb", std::move(committed_tcb))); } - JS_CHECK_SET(a.set_uint32("current_minor", attestation.current_minor)); - JS_CHECK_SET(a.set_uint32("current_build", attestation.current_build)); - JS_CHECK_SET(a.set_uint32("current_major", attestation.current_major)); + JS_CHECK_SET(a.set_uint32("current_minor", attestation.current_minor())); + JS_CHECK_SET(a.set_uint32("current_build", attestation.current_build())); + JS_CHECK_SET(a.set_uint32("current_major", attestation.current_major())); JS_CHECK_SET( - a.set_uint32("committed_build", attestation.committed_build)); + a.set_uint32("committed_build", attestation.committed_build())); JS_CHECK_SET( - a.set_uint32("committed_minor", attestation.committed_minor)); + a.set_uint32("committed_minor", attestation.committed_minor())); JS_CHECK_SET( - a.set_uint32("committed_major", attestation.committed_major)); + a.set_uint32("committed_major", attestation.committed_major())); { auto launch_tcb = - jsctx.wrap(make_js_tcb_version(jsctx, attestation.launch_tcb)); + jsctx.wrap(make_js_tcb_version(jsctx, attestation.launch_tcb())); JS_CHECK_EXC(launch_tcb); JS_CHECK_SET(a.set("launch_tcb", std::move(launch_tcb))); } @@ -289,13 +289,15 @@ namespace ccf::js::extensions JS_CHECK_EXC(signature); { - auto signature_r = jsctx.new_array_buffer_copy(attestation.signature.r); + auto signature_r = + jsctx.new_array_buffer_copy(attestation.signature_r()); JS_CHECK_EXC(signature_r); JS_CHECK_SET(signature.set("r", std::move(signature_r))); } { - auto signature_s = jsctx.new_array_buffer_copy(attestation.signature.s); + auto signature_s = + jsctx.new_array_buffer_copy(attestation.signature_s()); JS_CHECK_EXC(signature_s); JS_CHECK_SET(signature.set("s", std::move(signature_s))); } diff --git a/src/node/node_state.h b/src/node/node_state.h index ba87f3778874..858cd0224db7 100644 --- a/src/node/node_state.h +++ b/src/node/node_state.h @@ -884,7 +884,7 @@ namespace ccf AttestationProvider::get_snp_attestation(quote_info); if (snp_attestation.has_value()) { - snp_tcb_version = snp_attestation.value().reported_tcb; + snp_tcb_version = snp_attestation.value().reported_tcb(); } // Verify that the security policy matches the quoted digest of the policy @@ -1032,19 +1032,13 @@ namespace ccf // Check that tcbm in endorsement matches reported TCB in our // retrieved attestation - const auto* quote = - reinterpret_cast( - quote_info.quote.data()); - const auto reported_tcb = quote->reported_tcb; - - // tcbm is a single hex value, like DB18000000000004. To match - // that with a TcbVersion, reverse the bytes. - const auto* tcb_begin = - reinterpret_cast(&reported_tcb); - const std::span tcb_bytes{ - tcb_begin, tcb_begin + sizeof(reported_tcb)}; - auto tcb_as_hex = fmt::format( - "{:02x}", fmt::join(tcb_bytes.rbegin(), tcb_bytes.rend(), "")); + const auto report = + ccf::pal::snp::parse_attestation_report_unverified( + quote_info.quote); + const auto reported_tcb = report.reported_tcb(); + + // tcbm is a single hex value, like DB18000000000004. + auto tcb_as_hex = reported_tcb.to_hex(); ccf::nonstd::to_upper(tcb_as_hex); if (tcb_as_hex == aci_endorsements.tcbm) diff --git a/src/node/quote.cpp b/src/node/quote.cpp index 38246d01f549..0f389bf5aa12 100644 --- a/src/node/quote.cpp +++ b/src/node/quote.cpp @@ -150,8 +150,8 @@ namespace ccf return measurement; } - std::optional AttestationProvider::get_snp_attestation( - const QuoteInfo& quote_info) + std::optional AttestationProvider:: + get_snp_attestation(const QuoteInfo& quote_info) { if (quote_info.format != QuoteFormat::amd_sev_snp_v1) { @@ -161,10 +161,7 @@ namespace ccf { pal::PlatformAttestationMeasurement d = {}; pal::PlatformAttestationReportData r = {}; - pal::verify_quote(quote_info, d, r); - auto attestation = *reinterpret_cast( - quote_info.quote.data()); - return attestation; + return pal::verify_snp_attestation_report_and_get(quote_info, d, r); } catch (const std::exception& e) { @@ -202,13 +199,10 @@ namespace ccf pal::PlatformAttestationReportData r = {}; try { - pal::verify_quote(quote_info, d, r); - auto quote = *reinterpret_cast( - quote_info.quote.data()); - std::copy( - std::begin(quote.host_data), - std::end(quote.host_data), - rep.begin()); + const auto report = + pal::verify_snp_attestation_report_and_get(quote_info, d, r); + const auto host_data = report.host_data(); + std::copy(host_data.begin(), host_data.end(), rep.begin()); } catch (const std::exception& e) { @@ -276,9 +270,8 @@ namespace ccf pal::PlatformAttestationMeasurement d = {}; pal::PlatformAttestationReportData r = {}; - pal::verify_quote(quote_info, d, r); auto attestation = - *reinterpret_cast(quote_info.quote.data()); + pal::verify_snp_attestation_report_and_get(quote_info, d, r); std::optional min_tcb_opt = std::nullopt; auto* h = tx.ro(Tables::SNP_TCB_VERSIONS); @@ -287,9 +280,9 @@ namespace ccf const std::string& cpuid_hex, const pal::snp::TcbVersionPolicy& 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) + 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; @@ -304,9 +297,9 @@ namespace ccf // CPUID of the attested cpu must now be equal to the min_tcb_opt's cpuid auto product_family = pal::snp::get_sev_snp_product( - attestation.cpuid_fam_id, attestation.cpuid_mod_id); + attestation.cpuid_fam_id(), attestation.cpuid_mod_id()); auto attestation_tcb_policy = - attestation.reported_tcb.to_policy(product_family); + attestation.reported_tcb().to_policy(product_family); if (pal::snp::TcbVersionPolicy::is_valid( min_tcb_opt.value(), attestation_tcb_policy)) diff --git a/src/pal/attestation.cpp b/src/pal/attestation.cpp index 3edd7268716b..f0376e466b38 100644 --- a/src/pal/attestation.cpp +++ b/src/pal/attestation.cpp @@ -15,6 +15,235 @@ namespace ccf::pal { + namespace snp + { + class AttestationReport::Impl + { + public: + TavAttestationReportPtr report; + + explicit Impl(TavAttestationReportPtr&& report_) : + report(std::move(report_)) + {} + }; + + class AttestationReportFactory + { + public: + static AttestationReport make(TavAttestationReportPtr&& report) + { + return AttestationReport( + std::make_unique(std::move(report))); + } + }; + + AttestationReport::AttestationReport(std::unique_ptr impl_) : + impl(std::move(impl_)) + {} + AttestationReport::AttestationReport(AttestationReport&&) noexcept = + default; + AttestationReport& AttestationReport::operator=( + AttestationReport&&) noexcept = default; + AttestationReport::~AttestationReport() = default; + + namespace + { + using BytesAccessor = + void (*)(const TavSnpAttestationReport*, const uint8_t**, size_t*); + + std::vector get_bytes( + const TavSnpAttestationReport* report, + BytesAccessor accessor, + size_t expected_size, + std::string_view field) + { + const uint8_t* data = nullptr; + size_t size = 0; + accessor(report, &data, &size); + if (size != expected_size || data == nullptr) + { + throw std::logic_error(fmt::format( + "SEV-SNP: TAV returned {} bytes for {} (data {}), expected {}", + size, + field, + data == nullptr ? "is null" : "is not null", + expected_size)); + } + return {data, data + size}; + } + + [[noreturn]] void throw_tav_error( + std::string_view operation, const TavError* error) + { + const auto error_code = tav_error_code(error); + const auto* error_message = tav_error_message(error); + throw std::logic_error(fmt::format( + "SEV-SNP: TAV {} failed ({}): {}", + operation, + static_cast(error_code), + error_message == nullptr ? "Unknown TAV error" : error_message)); + } + } + +#define SNP_SCALAR_ACCESSOR(method, tav_accessor, type) \ + type AttestationReport::method() const \ + { \ + return tav_accessor(impl->report.get()); \ + } + + SNP_SCALAR_ACCESSOR(version, tav_snp_attestation_report_version, uint32_t) + SNP_SCALAR_ACCESSOR( + guest_svn, tav_snp_attestation_report_guest_svn, uint32_t) + SNP_SCALAR_ACCESSOR(policy, tav_snp_attestation_report_policy, uint64_t) + SNP_SCALAR_ACCESSOR( + policy_abi_minor, tav_snp_attestation_report_policy_abi_minor, uint8_t) + SNP_SCALAR_ACCESSOR( + policy_abi_major, tav_snp_attestation_report_policy_abi_major, uint8_t) + SNP_SCALAR_ACCESSOR(policy_smt, tav_snp_attestation_report_policy_smt, bool) + SNP_SCALAR_ACCESSOR( + policy_migrate_ma, tav_snp_attestation_report_policy_migrate_ma, bool) + SNP_SCALAR_ACCESSOR( + policy_debug, tav_snp_attestation_report_policy_debug, bool) + SNP_SCALAR_ACCESSOR( + policy_single_socket, + tav_snp_attestation_report_policy_single_socket, + bool) + SNP_SCALAR_ACCESSOR(vmpl, tav_snp_attestation_report_vmpl, uint32_t) + SNP_SCALAR_ACCESSOR( + signature_algo, tav_snp_attestation_report_signature_algo, uint32_t) + SNP_SCALAR_ACCESSOR( + platform_info, tav_snp_attestation_report_platform_info, uint64_t) + SNP_SCALAR_ACCESSOR(flags, tav_snp_attestation_report_flags, uint32_t) + SNP_SCALAR_ACCESSOR( + flags_author_key_en, tav_snp_attestation_report_flags_author_key_en, bool) + SNP_SCALAR_ACCESSOR( + flags_mask_chip_key, tav_snp_attestation_report_flags_mask_chip_key, bool) + SNP_SCALAR_ACCESSOR( + flags_signing_key, tav_snp_attestation_report_flags_signing_key, uint8_t) + SNP_SCALAR_ACCESSOR( + cpuid_fam_id, tav_snp_attestation_report_cpuid_fam_id, uint8_t) + SNP_SCALAR_ACCESSOR( + cpuid_mod_id, tav_snp_attestation_report_cpuid_mod_id, uint8_t) + SNP_SCALAR_ACCESSOR( + cpuid_step, tav_snp_attestation_report_cpuid_step, uint8_t) + SNP_SCALAR_ACCESSOR( + current_build, tav_snp_attestation_report_current_build, uint8_t) + SNP_SCALAR_ACCESSOR( + current_minor, tav_snp_attestation_report_current_minor, uint8_t) + SNP_SCALAR_ACCESSOR( + current_major, tav_snp_attestation_report_current_major, uint8_t) + SNP_SCALAR_ACCESSOR( + committed_build, tav_snp_attestation_report_committed_build, uint8_t) + SNP_SCALAR_ACCESSOR( + committed_minor, tav_snp_attestation_report_committed_minor, uint8_t) + SNP_SCALAR_ACCESSOR( + committed_major, tav_snp_attestation_report_committed_major, uint8_t) + +#undef SNP_SCALAR_ACCESSOR + +#define SNP_BYTES_ACCESSOR(method, tav_accessor, size) \ + std::vector AttestationReport::method() const \ + { \ + return get_bytes(impl->report.get(), tav_accessor, size, #method); \ + } + + SNP_BYTES_ACCESSOR(family_id, tav_snp_attestation_report_family_id, 16) + SNP_BYTES_ACCESSOR(image_id, tav_snp_attestation_report_image_id, 16) + SNP_BYTES_ACCESSOR( + report_data, + tav_snp_attestation_report_report_data, + snp_attestation_report_data_size) + SNP_BYTES_ACCESSOR( + measurement, + tav_snp_attestation_report_measurement, + snp_attestation_measurement_size) + SNP_BYTES_ACCESSOR(host_data, tav_snp_attestation_report_host_data, 32) + SNP_BYTES_ACCESSOR( + id_key_digest, tav_snp_attestation_report_id_key_digest, 48) + SNP_BYTES_ACCESSOR( + author_key_digest, tav_snp_attestation_report_author_key_digest, 48) + SNP_BYTES_ACCESSOR(report_id, tav_snp_attestation_report_report_id, 32) + SNP_BYTES_ACCESSOR( + report_id_ma, tav_snp_attestation_report_report_id_ma, 32) + SNP_BYTES_ACCESSOR(chip_id, tav_snp_attestation_report_chip_id, 64) + SNP_BYTES_ACCESSOR(signature_r, tav_snp_attestation_report_signature_r, 72) + SNP_BYTES_ACCESSOR(signature_s, tav_snp_attestation_report_signature_s, 72) + +#undef SNP_BYTES_ACCESSOR + + TcbVersionRaw AttestationReport::platform_version() const + { + return TcbVersionRaw(get_bytes( + impl->report.get(), + tav_snp_attestation_report_platform_version, + snp_tcb_version_size, + "platform_version")); + } + + TcbVersionRaw AttestationReport::reported_tcb() const + { + return TcbVersionRaw(get_bytes( + impl->report.get(), + tav_snp_attestation_report_reported_tcb, + snp_tcb_version_size, + "reported_tcb")); + } + + TcbVersionRaw AttestationReport::committed_tcb() const + { + return TcbVersionRaw(get_bytes( + impl->report.get(), + tav_snp_attestation_report_committed_tcb, + snp_tcb_version_size, + "committed_tcb")); + } + + TcbVersionRaw AttestationReport::launch_tcb() const + { + return TcbVersionRaw(get_bytes( + impl->report.get(), + tav_snp_attestation_report_launch_tcb, + snp_tcb_version_size, + "launch_tcb")); + } + + std::vector AttestationReport::chip_id_for_vcek() const + { + auto id = chip_id(); + const auto product = get_sev_snp_product(cpuid_fam_id(), cpuid_mod_id()); + if (product == ProductName::Milan || product == ProductName::Genoa) + { + return id; + } + if (product == ProductName::Turin) + { + id.resize(8); + return id; + } + throw std::logic_error( + fmt::format("Unsupported SEV-SNP product: {}", product)); + } + + AttestationReport parse_attestation_report_unverified( + std::span report) + { + TavSnpAttestationReport* raw_report = nullptr; + TavErrorPtr error(tav_snp_attestation_report_from_unverified_bytes( + report.data(), report.size(), &raw_report)); + TavAttestationReportPtr parsed_report(raw_report); + if (error != nullptr) + { + throw_tav_error("unverified report parsing", error.get()); + } + if (parsed_report == nullptr) + { + throw std::logic_error( + "SEV-SNP: TAV parsing succeeded without returning a report"); + } + return AttestationReportFactory::make(std::move(parsed_report)); + } + } + using Unique_ASN1_OBJECT = ccf::crypto::OpenSSL:: Unique_SSL_OBJECT; using Unique_ASN1_INTEGER = ccf::crypto::OpenSSL:: @@ -208,7 +437,7 @@ namespace ccf::pal } // Verifying SNP attestation report is available on all platforms. - void verify_snp_attestation_report( + snp::AttestationReport verify_snp_attestation_report_and_get( const QuoteInfo& quote_info, PlatformAttestationMeasurement& measurement, PlatformAttestationReportData& report_data) @@ -220,28 +449,6 @@ namespace ccf::pal quote_info.format)); } - if (quote_info.quote.size() != sizeof(snp::Attestation)) - { - throw std::logic_error(fmt::format( - "Input SEV-SNP attestation report is not of expected size {}: {}", - sizeof(snp::Attestation), - quote_info.quote.size())); - } - - auto quote = - *reinterpret_cast(quote_info.quote.data()); - - if (quote.version < snp::minimum_attestation_version) - { - throw std::logic_error(fmt::format( - "SEV-SNP: Attestation version is {} not >= expected minimum {}", - quote.version, - snp::minimum_attestation_version)); - } - - auto product_family = - snp::get_sev_snp_product(quote.cpuid_fam_id, quote.cpuid_mod_id); - // ---- Verify certificate chain ---- auto certificates = ccf::crypto::split_x509_cert_bundle(std::string_view( @@ -287,17 +494,33 @@ namespace ccf::pal "SEV-SNP: TAV verification succeeded without returning a report"); } + auto attestation = + snp::AttestationReportFactory::make(std::move(verified_report)); + + if (attestation.version() < snp::minimum_attestation_version) + { + throw std::logic_error(fmt::format( + "SEV-SNP: Attestation version is {} not >= expected minimum {}", + attestation.version(), + snp::minimum_attestation_version)); + } + + const auto product_family = snp::get_sev_snp_product( + attestation.cpuid_fam_id(), attestation.cpuid_mod_id()); + // ---- Verify attestation report contents ---- - if (quote.flags.signing_key != snp::attestation_flags_signing_key_vcek) + if ( + attestation.flags_signing_key() != + snp::attestation_flags_signing_key_vcek) { throw std::logic_error(fmt::format( "SEV-SNP: Attestation report must be signed by VCEK: {}", - static_cast(quote.flags.signing_key))); + attestation.flags_signing_key())); } // mask_chip_key if set means the operator set the vcek to 0s - if (quote.flags.mask_chip_key != 0) + if (attestation.flags_mask_chip_key()) { throw std::logic_error( fmt::format("SEV-SNP: Mask chip key must not be set")); @@ -306,15 +529,15 @@ namespace ccf::pal // All attestation reports generated by guests must have VMPL <= 3 // while host generated reports have VMPL > 3. // We should reject host generated reports. - if (quote.vmpl > 3) + if (attestation.vmpl() > 3) { throw std::logic_error(fmt::format( "SEV-SNP: This report seems to be host generated (VMPL {} > 3)", - quote.vmpl)); + attestation.vmpl())); } // Debug mode would allow decryption of guest pages - if (quote.policy.debug != 0) + if (attestation.policy_debug()) { throw std::logic_error( "SEV-SNP: SNP attestation report guest policy debugging must not be " @@ -323,7 +546,7 @@ namespace ccf::pal // Migration of CCF nodes and other services could allow duplicates, and // hence must be disallowed - if (quote.policy.migrate_ma != 0) + if (attestation.policy_migrate_ma()) { throw std::logic_error( "SEV-SNP: SNP attestation report guest policy migration must not be " @@ -334,7 +557,7 @@ namespace ccf::pal if (endorsed_tcb.has_value()) { auto endorsed_tcb_policy = endorsed_tcb->to_policy(product_family); - auto reported_tcb = quote.reported_tcb.to_policy(product_family); + auto reported_tcb = attestation.reported_tcb().to_policy(product_family); if (!snp::TcbVersionPolicy::is_valid(endorsed_tcb_policy, reported_tcb)) { @@ -347,7 +570,7 @@ namespace ccf::pal } auto endorsed_chip_id = get_endorsed_chip_id_from_cert(vcek_cert); - auto reported_chip_id = quote.get_chip_id_for_vcek(); + auto reported_chip_id = attestation.chip_id_for_vcek(); if ( endorsed_chip_id.has_value() && (endorsed_chip_id->size() != reported_chip_id.size() || @@ -368,10 +591,11 @@ namespace ccf::pal const auto& quote_endorsed_tcb = quote_info.endorsed_tcb.value(); auto raw_endorsed_tcb = snp::TcbVersionRaw::from_hex(quote_endorsed_tcb); - if (raw_endorsed_tcb != quote.reported_tcb) + const auto reported_tcb = attestation.reported_tcb(); + if (raw_endorsed_tcb != reported_tcb) { auto endorsed_tcb_hex = raw_endorsed_tcb.to_hex(); - auto report_tcb_hex = quote.reported_tcb.to_hex(); + auto report_tcb_hex = reported_tcb.to_hex(); throw std::logic_error(fmt::format( "SEV-SNP: endorsed TCB {} does not match reported TCB {}", endorsed_tcb_hex, @@ -381,8 +605,17 @@ namespace ccf::pal // ---- Set return values ---- - report_data = SnpAttestationReportData(quote.report_data); - measurement = SnpAttestationMeasurement(quote.measurement); + report_data = SnpAttestationReportData(attestation.report_data()); + measurement = SnpAttestationMeasurement(attestation.measurement()); + return attestation; + } + + void verify_snp_attestation_report( + const QuoteInfo& quote_info, + PlatformAttestationMeasurement& measurement, + PlatformAttestationReportData& report_data) + { + verify_snp_attestation_report_and_get(quote_info, measurement, report_data); } void verify_quote( diff --git a/src/pal/quote_generation.h b/src/pal/quote_generation.h index 72ec00b8ddea..676c88eefd0c 100644 --- a/src/pal/quote_generation.h +++ b/src/pal/quote_generation.h @@ -90,24 +90,25 @@ namespace ccf::pal QuoteInfo node_quote_info = {}; node_quote_info.format = QuoteFormat::amd_sev_snp_v1; auto attestation = snp::get_attestation(report_data); + node_quote_info.quote = attestation->get_raw(); + auto report = + snp::parse_attestation_report_unverified(node_quote_info.quote); - if (attestation->get().version < pal::snp::minimum_attestation_version) + if (report.version() < pal::snp::minimum_attestation_version) { throw std::logic_error(fmt::format( "SEV-SNP: attestation version {} is less than the minimum supported " "version {}", - attestation->get().version, + report.version(), pal::snp::minimum_attestation_version)); } - node_quote_info.quote = attestation->get_raw(); - if (endorsement_cb != nullptr) { endorsement_cb( node_quote_info, snp::make_endorsement_endpoint_configuration( - attestation->get(), endorsements_servers)); + report, endorsements_servers)); } } diff --git a/src/pal/test/snp_attestation_validation.cpp b/src/pal/test/snp_attestation_validation.cpp index 4d64509b4217..51db80da74f1 100644 --- a/src/pal/test/snp_attestation_validation.cpp +++ b/src/pal/test/snp_attestation_validation.cpp @@ -128,6 +128,34 @@ namespace } } +TEST_CASE("unverified SNP report accessors") +{ + using namespace ccf::pal; + + auto report = + snp::parse_attestation_report_unverified(snp::testing::milan_attestation); + + CHECK(report.version() == 3); + CHECK(report.cpuid_fam_id() == 25); + CHECK(report.cpuid_mod_id() == 1); + CHECK(report.reported_tcb().to_hex() == "db18000000000004"); + const auto measurement = report.measurement(); + auto moved_report = std::move(report); + CHECK(measurement.size() == snp_attestation_measurement_size); + CHECK(moved_report.signature_r().size() == 72); +} + +TEST_CASE("unverified SNP report rejects invalid sizes") +{ + CHECK_THROWS_WITH_AS( + ccf::pal::snp::parse_attestation_report_unverified( + std::vector(100)), + doctest::Contains( + "SEV-SNP: TAV unverified report parsing failed (1): Invalid " + "attestation report: expected 1184 bytes, got 100"), + std::logic_error); +} + TEST_CASE("milan validation") { using namespace ccf; @@ -193,7 +221,8 @@ TEST_CASE("Invalid attestation signature fails TAV verification") using namespace ccf; auto invalid_attestation = pal::snp::testing::milan_attestation; - invalid_attestation[offsetof(pal::snp::Attestation, signature)] ^= 1; + static constexpr size_t signature_offset = 0x2a0; + invalid_attestation[signature_offset] ^= 1; auto quote_info = QuoteInfo{ .format = QuoteFormat::amd_sev_snp_v1, .quote = std::move(invalid_attestation), @@ -289,11 +318,11 @@ TEST_CASE("Parsing of Tcb versions from strings") TEST_CASE("Parsing tcb versions from attestaion") { - auto milan_attestation = *reinterpret_cast( - ccf::pal::snp::testing::milan_attestation.data()); - auto milan_tcb = - milan_attestation.reported_tcb.to_policy(ccf::pal::snp::ProductName::Milan) - .to_milan_genoa(); + auto milan_attestation = ccf::pal::snp::parse_attestation_report_unverified( + ccf::pal::snp::testing::milan_attestation); + auto milan_tcb = milan_attestation.reported_tcb() + .to_policy(ccf::pal::snp::ProductName::Milan) + .to_milan_genoa(); CHECK_EQ(milan_tcb.microcode, 0xdb); CHECK_EQ(milan_tcb.snp, 0x18); CHECK_EQ(milan_tcb.tee, 0x00); @@ -508,7 +537,7 @@ TEST_CASE("Quote endorsements url generation") for (auto [attestation, servers, expected_url] : test_cases) { auto quote = - *reinterpret_cast(attestation.data()); + ccf::pal::snp::parse_attestation_report_unverified(attestation); auto config = ccf::pal::snp::make_endorsement_endpoint_configuration(quote, servers); @@ -519,12 +548,12 @@ TEST_CASE("Quote endorsements url generation") TEST_CASE("Quote endorsements generation for v2 attestation version fails") { auto v2_format_milan_attestation = - *reinterpret_cast( - ccf::pal::snp::testing::v2_format_milan_attestation.data()); + ccf::pal::snp::parse_attestation_report_unverified( + ccf::pal::snp::testing::v2_format_milan_attestation); - CHECK_EQ(v2_format_milan_attestation.version, 2); - CHECK_EQ(v2_format_milan_attestation.cpuid_fam_id, 0x0); - CHECK_EQ(v2_format_milan_attestation.cpuid_mod_id, 0x0); + CHECK_EQ(v2_format_milan_attestation.version(), 2); + CHECK_EQ(v2_format_milan_attestation.cpuid_fam_id(), 0x0); + CHECK_EQ(v2_format_milan_attestation.cpuid_mod_id(), 0x0); CHECK_THROWS_WITH( ccf::pal::snp::make_endorsement_endpoint_configuration( @@ -550,8 +579,8 @@ TEST_CASE("Extracting metadata from endorsements") .uvm_endorsements = std::nullopt, }; - auto attestation = *reinterpret_cast( - milan_quote_info.quote.data()); + auto attestation = + pal::snp::parse_attestation_report_unverified(milan_quote_info.quote); auto certificates = ccf::crypto::split_x509_cert_bundle(std::string_view( reinterpret_cast(milan_quote_info.endorsements.data()), @@ -564,12 +593,11 @@ TEST_CASE("Extracting metadata from endorsements") REQUIRE(endorsed_tcb.has_value()); CHECK_EQ( nlohmann::json(endorsed_tcb.value()).dump(), - nlohmann::json(attestation.reported_tcb).dump()); + nlohmann::json(attestation.reported_tcb()).dump()); auto endorsed_chip_id = pal::get_endorsed_chip_id_from_cert(chip_certificate); REQUIRE(endorsed_chip_id.has_value()); - auto printable_reported_chip_id = std::span( - attestation.chip_id, attestation.chip_id + sizeof(attestation.chip_id)); + auto printable_reported_chip_id = attestation.chip_id(); CHECK_EQ( ds::to_hex(endorsed_chip_id.value()), ds::to_hex(printable_reported_chip_id)); diff --git a/src/pal/test/snp_ioctl_test.cpp b/src/pal/test/snp_ioctl_test.cpp index f873733d173e..418fbf810c45 100644 --- a/src/pal/test/snp_ioctl_test.cpp +++ b/src/pal/test/snp_ioctl_test.cpp @@ -25,9 +25,10 @@ TEST_CASE("SNP request attestation") PlatformAttestationReportData report_data(snp_report_data); snp::ioctl6::Attestation ioctl_attestation(report_data); - const snp::Attestation& attestation = ioctl_attestation.get(); + const auto attestation = + snp::parse_attestation_report_unverified(ioctl_attestation.get_raw()); - SnpAttestationReportData attested_report_data(attestation.report_data); + SnpAttestationReportData attested_report_data(attestation.report_data()); REQUIRE_EQ(snp_report_data.report_data, attested_report_data.report_data); } diff --git a/src/pal/test/verify_attestation.cpp b/src/pal/test/verify_attestation.cpp index b92492d07797..115482e8a075 100644 --- a/src/pal/test/verify_attestation.cpp +++ b/src/pal/test/verify_attestation.cpp @@ -18,8 +18,8 @@ void fetch_endorsements( const std::vector& attestation_raw, std::vector& output) { - auto attestation = *reinterpret_cast( - attestation_raw.data()); + auto attestation = + ccf::pal::snp::parse_attestation_report_unverified(attestation_raw); auto endorsement_config = ccf::pal::snp::make_endorsement_endpoint_configuration( @@ -64,15 +64,16 @@ int main(int argc, char** argv) .add_option( "-a,--attestation", attestation_hex, "Attestation in hex format") ->check([](const std::string& attestation_hex) { - auto attest = ccf::ds::from_hex(attestation_hex); - if (attest.size() != sizeof(ccf::pal::snp::Attestation)) + try { - return std::string(fmt::format( - "Attestation size is incorrect {} != {}", - attest.size(), - sizeof(ccf::pal::snp::Attestation))); + ccf::pal::snp::parse_attestation_report_unverified( + ccf::ds::from_hex(attestation_hex)); + return std::string(); + } + catch (const std::exception& e) + { + return std::string(e.what()); } - return std::string(); }); ccf::LoggerLevel log_level = ccf::LoggerLevel::INFO; diff --git a/src/pal/test/verify_uvm_attestation_and_endorsements.cpp b/src/pal/test/verify_uvm_attestation_and_endorsements.cpp index f4822e7ce583..86f1619113a7 100644 --- a/src/pal/test/verify_uvm_attestation_and_endorsements.cpp +++ b/src/pal/test/verify_uvm_attestation_and_endorsements.cpp @@ -241,12 +241,11 @@ int main(int argc, char** argv) "Expected SNP quote format"); LOG_INFO_FMT("Verifying endorsements"); - const auto* attestation_unverified = - reinterpret_cast( - quote_info.quote.data()); + const auto attestation_unverified = + ccf::pal::snp::parse_attestation_report_unverified(quote_info.quote); validate_endorsements( endorsements, - attestation_unverified->reported_tcb, + attestation_unverified.reported_tcb(), quote_info.endorsements); LOG_INFO_FMT("Verifying quote"); diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index f106a17b052b..925ef8912222 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -290,7 +290,7 @@ dependencies = [ [[package]] name = "tee-attestation-verification-caci" -version = "1.0.4" +version = "1.0.7" dependencies = [ "serde_json", "tee-attestation-verification-cose", @@ -300,7 +300,7 @@ dependencies = [ [[package]] name = "tee-attestation-verification-cose" -version = "1.0.4" +version = "1.0.7" dependencies = [ "cborrs", "cborrs-nondet", @@ -309,7 +309,7 @@ dependencies = [ [[package]] name = "tee-attestation-verification-crypto" -version = "1.0.4" +version = "1.0.7" dependencies = [ "foreign-types", "js-sys", @@ -321,7 +321,7 @@ dependencies = [ [[package]] name = "tee-attestation-verification-ffi" -version = "1.0.4" +version = "1.0.7" dependencies = [ "js-sys", "serde_json", @@ -336,7 +336,7 @@ dependencies = [ [[package]] name = "tee-attestation-verification-lib" -version = "1.0.4" +version = "1.0.7" dependencies = [ "log", "tee-attestation-verification-crypto", diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index f332af2a16b0..00f0881d827f 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -964,35 +964,35 @@ namespace ccf } static void trust_node_snp_tcb_version( - ccf::kv::Tx& tx, pal::snp::Attestation& attestation) + ccf::kv::Tx& tx, const pal::snp::AttestationReport& attestation) { - if (attestation.version < pal::snp::minimum_attestation_version) + if (attestation.version() < pal::snp::minimum_attestation_version) { throw std::logic_error(fmt::format( "SEV-SNP: attestation version {} is not supported. Minimum " "supported version is {}", - attestation.version, + attestation.version(), pal::snp::minimum_attestation_version)); } // As cpuid -> attestation cpuid is surjective, we must use the local // cpuid and validate it against the attestation's cpuid auto cpuid = pal::snp::get_cpuid_untrusted(); if ( - cpuid.get_family_id() != attestation.cpuid_fam_id || - cpuid.get_model_id() != attestation.cpuid_mod_id || - cpuid.stepping != attestation.cpuid_step) + cpuid.get_family_id() != attestation.cpuid_fam_id() || + cpuid.get_model_id() != attestation.cpuid_mod_id() || + cpuid.stepping != attestation.cpuid_step()) { throw std::runtime_error(fmt::format( "CPU-sourced cpuid does not match attestation cpuid ({} != {}, {}, " "{})", cpuid.hex_str(), - attestation.cpuid_fam_id, - attestation.cpuid_mod_id, - attestation.cpuid_step)); + attestation.cpuid_fam_id(), + attestation.cpuid_mod_id(), + attestation.cpuid_step())); } auto* h = tx.wo(Tables::SNP_TCB_VERSIONS); auto product = pal::snp::get_sev_snp_product(cpuid); - h->put(cpuid.hex_str(), attestation.reported_tcb.to_policy(product)); + h->put(cpuid.hex_str(), attestation.reported_tcb().to_policy(product)); } static void init_configuration( From 7f5abff4bf66faa23dca1cde4ebcc97e8be38d5b Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Wed, 9 Sep 2026 11:47:26 +0100 Subject: [PATCH 04/14] Preserve SNP compatibility around TAV-backed attestation Keep deprecated SNP layouts and APIs while using borrowed TAV report views and byte-only ioctl acquisition internally. Export the Rust archive's native dependencies and add an isolated link regression test. Document the firmware-field correction and pending changes for 7.0.15. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e97cc978-adf6-4072-bd60-c1d9b51096d8 --- CHANGELOG.md | 15 +- CMakeLists.txt | 20 +- cmake/ccf_rs.cmake | 18 ++ cmake/crypto.cmake | 8 +- include/ccf/node/quote.h | 9 +- include/ccf/pal/attestation.h | 1 + include/ccf/pal/attestation_sev_snp.h | 265 ++++++++++++++++-- include/ccf/pal/snp_ioctl.h | 11 + include/ccf/pal/snp_ioctl6.h | 124 ++++++-- python/pyproject.toml | 2 +- src/js/extensions/snp_attestation.cpp | 8 +- src/node/node_state.h | 4 +- src/node/quote.cpp | 50 +++- src/node/rpc/node_frontend.h | 3 +- src/pal/attestation.cpp | 104 ++++--- src/pal/quote_generation.h | 5 +- src/pal/test/snp_attestation_validation.cpp | 257 ++++++++++++++++- src/pal/test/snp_ioctl_test.cpp | 6 +- src/pal/test/verify_attestation.cpp | 4 +- ...erify_uvm_attestation_and_endorsements.cpp | 2 +- src/rust/Cargo.lock | 134 +++++---- src/rust/test/native_link.c | 34 +++ 22 files changed, 915 insertions(+), 169 deletions(-) create mode 100644 src/rust/test/native_link.c diff --git a/CHANGELOG.md b/CHANGELOG.md index 80b14fcb9eb3..3e5fbecf2876 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [7.0.15] + +[7.0.15]: https://github.com/microsoft/CCF/releases/tag/ccf-7.0.15 + +### Fixed + +- Fixed swapped `current_minor` and `current_build` values in JavaScript `verifySnpAttestation()` results. These fields now match the AMD SEV-SNP report layout. The deprecated C++ `ccf::pal::snp::Attestation` retains its previous field mapping for compatibility. (#8083) +- The exported `ccf_rs` CMake target now supplies its OpenSSL link dependencies, so downstream consumers no longer need to add them manually. (#8083) + +### Changed + +- SNP attestation reports are now parsed and verified through the move-only, TAV-backed `ccf::pal::snp::AttestationReport` accessor API. Byte accessors return read-only spans into the report; moves preserve these views, but destroying or replacing the owning report invalidates them. The packed `ccf::pal::snp::Attestation` wire-layout type and its legacy accessors remain available for compatibility but are deprecated. (#8083) +- `ccf::pal::snp::get_attestation_bytes()` in `ccf/pal/snp_ioctl.h` requests an unverified SNP report as owned bytes, without using the legacy report type. `AttestationInterface::get_raw()` and its ioctl implementation remain available but are deprecated. (#8083) + ## [7.0.14] [7.0.14]: https://github.com/microsoft/CCF/releases/tag/ccf-7.0.14 @@ -87,7 +101,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed -- SNP attestation reports are now parsed and verified through TAV accessors. The public packed `ccf::pal::snp::Attestation` wire-layout type has been replaced by the move-only `ccf::pal::snp::AttestationReport` accessor API. (#8083) - TLS handshakes now prefer hybrid post-quantum key exchange groups, in the order `SecP384r1MLKEM1024`, `SecP256r1MLKEM768`, `X25519MLKEM768`, when the linked crypto provider supports them. The `P-521`, `P-384` and `P-256` groups are retained as fallbacks (#8107). - `ccf.cose.verify_receipt()` has moved and been renamed to `ccf.receipt.verify_cose()`; the old name still works but is deprecated (#8109). diff --git a/CMakeLists.txt b/CMakeLists.txt index 495b51bf745d..862725679625 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -257,7 +257,7 @@ install(TARGETS http_parser EXPORT ccf DESTINATION lib) add_ccf_static_library( ccf_pal SRCS ${CCF_DIR}/src/pal/attestation.cpp - LINK_LIBS ccfcrypto ${CMAKE_THREAD_LIBS_INIT} ${CMAKE_DL_LIBS} m + LINK_LIBS ccfcrypto ) target_include_directories( ccf_pal @@ -924,6 +924,24 @@ if(BUILD_TESTS) PRIVATE ${CMAKE_THREAD_LIBS_INIT} ccfcrypto ) + # Do not link ccfcrypto: it can mask missing Rust native dependencies. + add_executable(ccf_rs_link_test src/rust/test/native_link.c) + target_include_directories( + ccf_rs_link_test + PRIVATE + ${CCF_DIR}/3rdparty/internal/tee-attestation-verification/ffi/include + ) + target_link_libraries(ccf_rs_link_test PRIVATE ccf_rs) + target_link_options( + ccf_rs_link_test + PRIVATE $<$:LINKER:--as-needed> + ) + add_warning_checks(ccf_rs_link_test) + add_san(ccf_rs_link_test) + add_test(NAME ccf_rs_link_test COMMAND ccf_rs_link_test) + set_property(TEST ccf_rs_link_test APPEND PROPERTY LABELS unit) + add_san_test_properties(ccf_rs_link_test) + add_unit_test(pem_test ${CMAKE_CURRENT_SOURCE_DIR}/src/crypto/test/pem.cpp) target_link_libraries(pem_test PRIVATE ${CMAKE_THREAD_LIBS_INIT}) diff --git a/cmake/ccf_rs.cmake b/cmake/ccf_rs.cmake index 3dbac6ed9c67..eecf172092c8 100644 --- a/cmake/ccf_rs.cmake +++ b/cmake/ccf_rs.cmake @@ -82,4 +82,22 @@ add_custom_target( VERBATIM ) +add_library(ccf_rs INTERFACE) +target_link_libraries( + ccf_rs + INTERFACE + $ + $ + 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) diff --git a/cmake/crypto.cmake b/cmake/crypto.cmake index 0efcee0742dd..9724c451fc1f 100644 --- a/cmake/crypto.cmake +++ b/cmake/crypto.cmake @@ -44,13 +44,7 @@ add_hardening(ccfcrypto) add_tidy(ccfcrypto) target_link_libraries(ccfcrypto PUBLIC crypto ssl evercbor ccf_threading) -target_link_libraries( - ccfcrypto - PUBLIC - $ - $ -) -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) diff --git a/include/ccf/node/quote.h b/include/ccf/node/quote.h index 182c010c4349..b28442e045ec 100644 --- a/include/ccf/node/quote.h +++ b/include/ccf/node/quote.h @@ -39,8 +39,15 @@ namespace ccf static std::optional get_host_data(const QuoteInfo& quote_info); - static std::optional get_snp_attestation( +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + [[deprecated("Use get_snp_attestation_report")]] + static std::optional get_snp_attestation( const QuoteInfo& quote_info); +#pragma GCC diagnostic pop + + static std::optional + get_snp_attestation_report(const QuoteInfo& quote_info); static QuoteVerificationResult verify_quote_against_store( ccf::kv::ReadOnlyTx& tx, diff --git a/include/ccf/pal/attestation.h b/include/ccf/pal/attestation.h index f5aa722f709e..452b3ed7bf0f 100644 --- a/include/ccf/pal/attestation.h +++ b/include/ccf/pal/attestation.h @@ -29,6 +29,7 @@ namespace ccf::pal PlatformAttestationMeasurement& measurement, PlatformAttestationReportData& report_data); + [[deprecated("Use snp::AttestationReport::verify")]] snp::AttestationReport verify_snp_attestation_report_and_get( const QuoteInfo& quote_info, PlatformAttestationMeasurement& measurement, diff --git a/include/ccf/pal/attestation_sev_snp.h b/include/ccf/pal/attestation_sev_snp.h index f64c554bbd39..96b4823cdcdd 100644 --- a/include/ccf/pal/attestation_sev_snp.h +++ b/include/ccf/pal/attestation_sev_snp.h @@ -14,11 +14,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include namespace ccf::pal::snp @@ -28,6 +30,81 @@ namespace ccf::pal::snp static constexpr auto NO_SECURITY_POLICY = ""; + [[deprecated("TAV verifies AMD root signing keys internally")]] + inline constexpr auto amd_milan_root_signing_public_key = + R"(-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0Ld52RJOdeiJlqK2JdsV +mD7FktuotWwX1fNgW41XY9Xz1HEhSUmhLz9Cu9DHRlvgJSNxbeYYsnJfvyjx1MfU +0V5tkKiU1EesNFta1kTA0szNisdYc9isqk7mXT5+KfGRbfc4V/9zRIcE8jlHN61S +1ju8X93+6dxDUrG2SzxqJ4BhqyYmUDruPXJSX4vUc01P7j98MpqOS95rORdGHeI5 +2Naz5m2B+O+vjsC060d37jY9LFeuOP4Meri8qgfi2S5kKqg/aF6aPtuAZQVR7u3K +FYXP59XmJgtcog05gmI0T/OitLhuzVvpZcLph0odh/1IPXqx3+MnjD97A7fXpqGd +/y8KxX7jksTEzAOgbKAeam3lm+3yKIcTYMlsRMXPcjNbIvmsBykD//xSniusuHBk +gnlENEWx1UcbQQrs+gVDkuVPhsnzIRNgYvM48Y+7LGiJYnrmE8xcrexekBxrva2V +9TJQqnN3Q53kt5viQi3+gCfmkwC0F0tirIZbLkXPrPwzZ0M9eNxhIySb2npJfgnq +z55I0u33wh4r0ZNQeTGfw03MBUtyuzGesGkcw+loqMaq1qR4tjGbPYxCvpCq7+Og +pCCoMNit2uLo9M18fHz10lOMT8nWAUvRZFzteXCm+7PHdYPlmQwUw3LvenJ/ILXo +QPHfbkH0CyPfhl1jWhJFZasCAwEAAQ== +-----END PUBLIC KEY----- +)"; + [[deprecated("TAV verifies AMD root signing keys internally")]] + inline constexpr auto amd_genoa_root_signing_public_key = + R"(-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA3Cd95S/uFOuRIskW9vz9 +VDBF69NDQF79oRhL/L2PVQGhK3YdfEBgpF/JiwWFBsT/fXDhzA01p3LkcT/7Ldjc +RfKXjHl+0Qq/M4dZkh6QDoUeKzNBLDcBKDDGWo3v35NyrxbA1DnkYwUKU5AAk4P9 +4tKXLp80oxt84ahyHoLmc/LqsGsp+oq1Bz4PPsYLwTG4iMKVaaT90/oZ4I8oibSr +u92vJhlqWO27d/Rxc3iUMyhNeGToOvgx/iUo4gGpG61NDpkEUvIzuKcaMx8IdTpW +g2DF6SwF0IgVMffnvtJmA68BwJNWo1E4PLJdaPfBifcJpuBFwNVQIPQEVX3aP89H +JSp8YbY9lySS6PlVEqTBBtaQmi4ATGmMR+n2K/e+JAhU2Gj7jIpJhOkdH9firQDn +mlA2SFfJ/Cc0mGNzW9RmIhyOUnNFoclmkRhl3/AQU5Ys9Qsan1jT/EiyT+pCpmnA ++y9edvhDCbOG8F2oxHGRdTBkylungrkXJGYiwGrR8kaiqv7NN8QhOBMqYjcbrkEr +0f8QMKklIS5ruOfqlLMCBw8JLB3LkjpWgtD7OpxkzSsohN47Uom86RY6lp72g8eX +HP1qYrnvhzaG1S70vw6OkbaaC9EjiH/uHgAJQGxon7u0Q7xgoREWA/e7JcBQwLg8 +0Hq/sbRuqesxz7wBWSY254cCAwEAAQ== +-----END PUBLIC KEY----- +)"; + [[deprecated("TAV verifies AMD root signing keys internally")]] + inline constexpr auto amd_turin_root_signing_public_key = + R"(-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwaAriB7EIuVc4ZB1wD3Y +fDxL+9eyS7+izm0Jj3W772NINCWl8Bj3w/JD2ZjmbRxWdIq/4d9iarCKorXloJUB +1jRdgxqccTx1aOoig4+2w1XhVVJT7K457wT5ZLNJgQaxqa9Etkwjd6+9sOhlCDE9 +l43kQ0R2BikVJa/uyyVOSwEk5w5tXKOuG9jvq6QtAMJasW38wlqRDaKEGtZ9VUgG +on27ZuL4sTJuC/azz9/iQBw8kEilzOl95AiTkeY5jSEBDWbAqnZk5qlM7kISKG20 +kgQm14mhNKDI2p2oua+zuAG7i52epoRF2GfU0TYk/yf+vCNB2tnechFQuP2e8bLk +95ZdqPi9/UWw4JXjtdEA4u2JYplSSUPQVAXKt6LVqujtJcM59JKr2u0XQ75KwxcM +p15gSXhBfInvPAwuAY4dEwwGqT8oIg4esPHwEsmChhYeDIxPG9R4fx9O0q6p8Gb+ +HXlTiS47P9YNeOpidOUKzDl/S1OvyhDtSL8LJc24QATFydo/iD/KUdvFTRlD0crk +AMkZLoWQ8hLDGc6BZJXsdd7Zf2e4UW3tI/1oh/2t23Ot3zyhTcv5gDbABu0LjVe9 +8uRnS15SMwK//lJt9e5BqKvgABkSoABf+B4VFtPVEX0ygrYaFaI9i5ABrxnVBmzX +pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== +-----END PUBLIC KEY----- +)"; + + 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 amd_root_signing_keys{ + {ProductName::Milan, + {amd_milan_root_signing_public_key, + "CN=ARK-Milan,O=Advanced Micro Devices,ST=CA,L=Santa Clara,C=US," + "OU=Engineering"}}, + {ProductName::Genoa, + {amd_genoa_root_signing_public_key, + "CN=ARK-Genoa,O=Advanced Micro Devices,ST=CA,L=Santa Clara,C=US," + "OU=Engineering"}}, + {ProductName::Turin, + {amd_turin_root_signing_public_key, + "CN=ARK-Turin,O=Advanced Micro Devices,ST=CA,L=Santa Clara,C=US," + "OU=Engineering"}}, + }; + #pragma pack(push, 1) // Table 3 constexpr size_t snp_tcb_version_size = 8; @@ -152,15 +229,21 @@ namespace ccf::pal::snp TcbVersionRaw() = default; - TcbVersionRaw(const std::vector& data) + TcbVersionRaw(const std::vector& data) : + TcbVersionRaw(from_span(data)) + {} + + static TcbVersionRaw from_span(std::span data) { if (data.size() != snp_tcb_version_size) { throw std::logic_error( fmt::format("Invalid TCB version raw data size: {}", data.size())); } + TcbVersionRaw tcb_version; std::memcpy( - static_cast(underlying_data), data.data(), snp_tcb_version_size); + tcb_version.underlying_data, data.data(), snp_tcb_version_size); + return tcb_version; } [[nodiscard]] std::vector data() const @@ -242,7 +325,121 @@ namespace ccf::pal::snp sizeof(TcbVersionRaw) == snp_tcb_version_size, "TCB version raw size mismatch"); - class AttestationReportFactory; + struct Signature + { + uint8_t r[72]; + uint8_t s[72]; + uint8_t reserved[512 - 144]; + }; + + // NOLINTNEXTLINE(performance-enum-size) + enum class SignatureAlgorithm : uint32_t + { + invalid = 0, + ecdsa_p384_sha384 = 1 + }; + +#pragma pack(push, 1) + struct GuestPolicy + { + uint8_t abi_minor; + uint8_t abi_major; + uint8_t smt : 1; + uint8_t reserved : 1; + uint8_t migrate_ma : 1; + uint8_t debug : 1; + uint8_t single_socket : 1; + uint64_t reserved2 : 43; + }; +#pragma pack(pop) + static_assert( + sizeof(GuestPolicy) == sizeof(uint64_t), + "Cannot cast GuestPolicy to uint64_t"); + +#pragma pack(push, 1) + struct Flags + { + uint8_t author_key_en : 1; + uint8_t mask_chip_key : 1; + uint8_t signing_key : 3; + uint64_t reserved : 27; + }; +#pragma pack(pop) + static_assert( + sizeof(Flags) == sizeof(uint32_t), "Cannot cast Flags to uint32_t"); + +#pragma pack(push, 1) + struct PlatformInfo + { + uint8_t smt_en : 1; + uint8_t tsme_en : 1; + uint64_t reserved : 62; + }; +#pragma pack(pop) + static_assert( + 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) + struct Attestation + { + uint32_t version = 0; /* 0x000 */ + uint32_t guest_svn = 0; /* 0x004 */ + struct GuestPolicy policy = {}; /* 0x008 */ + uint8_t family_id[16] = {0}; /* 0x010 */ + uint8_t image_id[16] = {0}; /* 0x020 */ + uint32_t vmpl = 0; /* 0x030 */ + SignatureAlgorithm signature_algo = {}; /* 0x034 */ + TcbVersionRaw platform_version; /* 0x038 */ + PlatformInfo platform_info = {}; /* 0x040 */ + Flags flags = {}; /* 0x048 */ + uint32_t reserved0 = 0; /* 0x04C */ + uint8_t report_data[snp_attestation_report_data_size] = {0}; /* 0x050 */ + uint8_t measurement[snp_attestation_measurement_size] = {0}; /* 0x090 */ + uint8_t host_data[32] = {0}; /* 0x0C0 */ + uint8_t id_key_digest[48] = {0}; /* 0x0E0 */ + uint8_t author_key_digest[48] = {0}; /* 0x110 */ + uint8_t report_id[32] = {0}; /* 0x140 */ + uint8_t report_id_ma[32] = {0}; /* 0x160 */ + TcbVersionRaw reported_tcb; /* 0x180 */ + uint8_t cpuid_fam_id = 0; /* 0x188 */ + uint8_t cpuid_mod_id = 0; /* 0x189 */ + uint8_t cpuid_step = 0; /* 0x18A */ + 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_major = 0; /* 0x1EA */ + uint8_t reserved2 = 0; /* 0x1EB */ + uint8_t committed_build = 0; /* 0x1EC */ + uint8_t committed_minor = 0; /* 0x1ED */ + uint8_t committed_major = 0; /* 0x1EE */ + uint8_t reserved3 = 0; /* 0x1EF */ + TcbVersionRaw launch_tcb; /* 0x1F0 */ + uint8_t reserved4[168] = {0}; /* 0x1F8 */ + struct Signature signature = {}; /* 0x2A0 */ + + [[nodiscard]] std::span 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)}; + } + if (product == ProductName::Turin) + { + return {chip_id, 8}; + } + throw std::logic_error( + fmt::format("Unsupported SEV-SNP product: {}", product)); + } + }; +#pragma pack(pop) class AttestationReport { @@ -251,9 +448,17 @@ namespace ccf::pal::snp std::unique_ptr impl; explicit AttestationReport(std::unique_ptr impl_); - friend class AttestationReportFactory; public: + static AttestationReport from_unverified(std::span report); + static AttestationReport verify( + std::span report, + std::span endorsements, + PlatformAttestationMeasurement& measurement, + PlatformAttestationReportData& report_data, + std::optional endorsed_tcb = std::nullopt); + + // Do not call accessors on a moved-from report. AttestationReport(AttestationReport&&) noexcept; AttestationReport& operator=(AttestationReport&&) noexcept; ~AttestationReport(); @@ -287,25 +492,29 @@ namespace ccf::pal::snp [[nodiscard]] uint8_t committed_minor() const; [[nodiscard]] uint8_t committed_major() const; - [[nodiscard]] std::vector family_id() const; - [[nodiscard]] std::vector image_id() const; + // Byte spans borrow report storage. Moves transfer ownership without + // invalidating these views. Destroying or replacing the owner invalidates + // them. + [[nodiscard]] std::span family_id() const; + [[nodiscard]] std::span image_id() const; [[nodiscard]] TcbVersionRaw platform_version() const; - [[nodiscard]] std::vector report_data() const; - [[nodiscard]] std::vector measurement() const; - [[nodiscard]] std::vector host_data() const; - [[nodiscard]] std::vector id_key_digest() const; - [[nodiscard]] std::vector author_key_digest() const; - [[nodiscard]] std::vector report_id() const; - [[nodiscard]] std::vector report_id_ma() const; + [[nodiscard]] std::span report_data() const; + [[nodiscard]] std::span measurement() const; + [[nodiscard]] std::span host_data() const; + [[nodiscard]] std::span id_key_digest() const; + [[nodiscard]] std::span author_key_digest() const; + [[nodiscard]] std::span report_id() const; + [[nodiscard]] std::span report_id_ma() const; [[nodiscard]] TcbVersionRaw reported_tcb() const; - [[nodiscard]] std::vector chip_id() const; - [[nodiscard]] std::vector chip_id_for_vcek() const; + [[nodiscard]] std::span chip_id() const; + [[nodiscard]] std::span chip_id_for_vcek() const; [[nodiscard]] TcbVersionRaw committed_tcb() const; [[nodiscard]] TcbVersionRaw launch_tcb() const; - [[nodiscard]] std::vector signature_r() const; - [[nodiscard]] std::vector signature_s() const; + [[nodiscard]] std::span signature_r() const; + [[nodiscard]] std::span signature_s() const; }; + [[deprecated("Use AttestationReport::from_unverified")]] AttestationReport parse_attestation_report_unverified( std::span report); @@ -329,7 +538,6 @@ namespace ccf::pal::snp } static constexpr uint8_t attestation_flags_signing_key_vcek = 0; - static constexpr size_t attestation_report_size = 1184; static constexpr uint32_t minimum_attestation_version = 3; static constexpr uint32_t attestation_policy_abi_major = 1; @@ -489,13 +697,34 @@ namespace ccf::pal::snp 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("e); + return make_endorsement_endpoint_configuration( + AttestationReport::from_unverified({report, attestation_report_size}), + endorsements_servers); + } + class AttestationInterface { public: + [[deprecated( + "Use get_attestation_bytes() from ccf/pal/snp_ioctl.h and " + "AttestationReport::from_unverified")]] [[nodiscard]] virtual const snp:: + Attestation& + get() const = 0; + [[deprecated("Use get_attestation_bytes() from ccf/pal/snp_ioctl.h")]] virtual std::vector get_raw() = 0; virtual ~AttestationInterface() = default; }; +#pragma GCC diagnostic pop } diff --git a/include/ccf/pal/snp_ioctl.h b/include/ccf/pal/snp_ioctl.h index 445a9b9a210f..d1fe21eea0d2 100644 --- a/include/ccf/pal/snp_ioctl.h +++ b/include/ccf/pal/snp_ioctl.h @@ -11,6 +11,17 @@ namespace ccf::pal::snp return ioctl6::supports_sev_snp(); } + // Request a fresh, unverified SNP report as owned bytes. + static std::vector get_attestation_bytes( + const PlatformAttestationReportData& report_data) + { + if (ioctl6::supports_sev_snp()) + { + return ioctl6::get_attestation_bytes(report_data); + } + throw std::logic_error("SEV-SNP not supported"); + } + static std::unique_ptr get_attestation( const PlatformAttestationReportData& report_data) { diff --git a/include/ccf/pal/snp_ioctl6.h b/include/ccf/pal/snp_ioctl6.h index 5814c6a94e02..7dbead593bb4 100644 --- a/include/ccf/pal/snp_ioctl6.h +++ b/include/ccf/pal/snp_ioctl6.h @@ -24,6 +24,19 @@ namespace ccf::pal::snp::ioctl6 { constexpr auto DEVICE = "/dev/sev-guest"; + namespace detail + { + constexpr size_t ATTESTATION_RESPONSE_SIZE = 4000; + constexpr size_t REPORT_SIZE_OFFSET = sizeof(uint32_t); + constexpr size_t REPORT_OFFSET = 0x20; + using AttestationResponseBytes = + std::array; + static_assert( + sizeof(AttestationResponseBytes) == ATTESTATION_RESPONSE_SIZE); + static_assert( + REPORT_OFFSET + attestation_report_size <= ATTESTATION_RESPONSE_SIZE); + } + #pragma pack(push, 1) // Helper to add padding to a struct, so that the resulting struct has some // minimum size. As a minor detail, the padding will be initialised to 0. @@ -110,19 +123,25 @@ namespace ccf::pal::snp::ioctl6 #pragma pack(pop) // Table 25 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" #pragma pack(push, 1) struct AttestationResp { uint32_t status = 0; uint32_t report_size = 0; uint8_t reserved[0x20 - 0x8] = {0}; - std::array report = {}; + [[deprecated("Use get_attestation_bytes()")]] + snp::Attestation report = {}; uint8_t padding[64] = {0}; // padding to the size of SEV_SNP_REPORT_RSP_BUF_SZ (i.e., 1280 bytes) }; #pragma pack(pop) - static_assert(offsetof(AttestationResp, report) == 0x20); + static_assert( + offsetof(AttestationResp, report_size) == detail::REPORT_SIZE_OFFSET); + static_assert(offsetof(AttestationResp, report) == detail::REPORT_OFFSET); static_assert(sizeof(AttestationResp) == 1280); +#pragma GCC diagnostic pop // Table 20 of the SEVSNP ABI constexpr uint8_t GUEST_FIELD_SELECT_GUEST_POLICY = 0b00000001; @@ -195,10 +214,21 @@ namespace ccf::pal::snp::ioctl6 using GuestRequestDerivedKey = GuestRequest; + namespace detail + { + using GuestRequestAttestationBytes = + GuestRequest; + static_assert( + sizeof(GuestRequestAttestationBytes) == sizeof(GuestRequestAttestation)); + } + // From linux/include/uapi/linux/sev-guest.h constexpr char SEV_GUEST_IOC_TYPE = 'S'; constexpr int SEV_SNP_GUEST_MSG_REPORT = - _IOWR(SEV_GUEST_IOC_TYPE, 0x0, GuestRequestAttestation); + _IOWR(SEV_GUEST_IOC_TYPE, 0x0, detail::GuestRequestAttestationBytes); + static_assert( + _IOWR(SEV_GUEST_IOC_TYPE, 0x0, detail::GuestRequestAttestationBytes) == + _IOWR(SEV_GUEST_IOC_TYPE, 0x0, GuestRequestAttestation)); constexpr int SEV_SNP_GUEST_MSG_DERIVED_KEY = _IOWR(SEV_GUEST_IOC_TYPE, 0x1, GuestRequestDerivedKey); @@ -207,14 +237,39 @@ namespace ccf::pal::snp::ioctl6 return access(DEVICE, W_OK) == 0; } - class Attestation : public AttestationInterface + namespace detail { - IoctlSentinel resp_with_sentinel; - PaddedAttestationResp& padded_resp = resp_with_sentinel.data; + inline void validate_report_size(uint32_t report_size) + { + if (report_size != attestation_report_size) + { + throw std::logic_error(fmt::format( + "Unexpected SEV-SNP attestation report size: {} != {}", + report_size, + attestation_report_size)); + } + } - public: - Attestation(const PlatformAttestationReportData& report_data) + inline std::vector extract_attestation_bytes( + const AttestationResponseBytes& response) + { + uint32_t report_size = 0; + std::memcpy( + &report_size, + response.data() + REPORT_SIZE_OFFSET, + sizeof(report_size)); + validate_report_size(report_size); + return { + response.begin() + REPORT_OFFSET, + response.begin() + REPORT_OFFSET + attestation_report_size}; + } + + template + void request_attestation( + const PlatformAttestationReportData& report_data, + IoctlSentinel& response) { + static_assert(sizeof(Response) == ATTESTATION_RESPONSE_SIZE); AttestationReq req = {}; if (report_data.data.size() <= snp_attestation_report_data_size) { @@ -237,8 +292,8 @@ namespace ccf::pal::snp::ioctl6 // Documented at // https://www.kernel.org/doc/html/latest/virt/coco/sev-guest.html - GuestRequestAttestation payload = { - .req_data = &req, .resp_wrapper = &padded_resp, .exit_info = {0}}; + GuestRequest payload = { + .req_data = &req, .resp_wrapper = &response.data, .exit_info = {0}}; int rc = ioctl(fd, SEV_SNP_GUEST_MSG_REPORT, &payload); if (rc < 0) @@ -252,7 +307,7 @@ namespace ccf::pal::snp::ioctl6 throw std::logic_error(msg); } - if (!resp_with_sentinel.sentinels_intact()) + if (!response.sentinels_intact()) { // This occurs if a kernel/firmware upgrade causes the response to // overflow our struct. If that happens, it is better to fail early than @@ -261,18 +316,49 @@ namespace ccf::pal::snp::ioctl6 "SEV_SNP_GUEST_MSG_REPORT IOCTL overwrote safety sentinels."); } } + } + + static std::vector get_attestation_bytes( + const PlatformAttestationReportData& report_data) + { + IoctlSentinel response; + response.data.fill(0); + detail::request_attestation(report_data, response); + return detail::extract_attestation_bytes(response.data); + } + class Attestation : public AttestationInterface + { + IoctlSentinel resp_with_sentinel; + PaddedAttestationResp& padded_resp = resp_with_sentinel.data; + + public: + Attestation(const PlatformAttestationReportData& report_data) + { + detail::request_attestation(report_data, resp_with_sentinel); + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + [[deprecated( + "Use get_attestation_bytes() and " + "AttestationReport::from_unverified")]] [[nodiscard]] const ccf::pal:: + snp::Attestation& + get() const override + { + detail::validate_report_size(padded_resp.report_size); + return padded_resp.report; + } + + [[deprecated("Use get_attestation_bytes()")]] std::vector get_raw() override { - if (padded_resp.report_size != attestation_report_size) - { - throw std::logic_error(fmt::format( - "Unexpected SEV-SNP attestation report size: {} != {}", - padded_resp.report_size, - attestation_report_size)); - } - return {padded_resp.report.begin(), padded_resp.report.end()}; + detail::validate_report_size(padded_resp.report_size); + const auto* report = + reinterpret_cast(&padded_resp.report); + return {report, report + attestation_report_size}; } +#pragma GCC diagnostic pop }; class DerivedKey diff --git a/python/pyproject.toml b/python/pyproject.toml index 7529d0383b9b..50017d2f73df 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "ccf" -version = "7.0.14" +version = "7.0.15" authors = [ { name="CCF Team", email="CCF-Sec@microsoft.com" }, ] diff --git a/src/js/extensions/snp_attestation.cpp b/src/js/extensions/snp_attestation.cpp index 14dc8f623b1f..5583cbb9a98f 100644 --- a/src/js/extensions/snp_attestation.cpp +++ b/src/js/extensions/snp_attestation.cpp @@ -103,8 +103,12 @@ namespace ccf::js::extensions try { - verified_attestation.emplace(pal::verify_snp_attestation_report_and_get( - quote_info, measurement, report_data)); + verified_attestation.emplace(pal::snp::AttestationReport::verify( + quote_info.quote, + quote_info.endorsements, + measurement, + report_data, + quote_info.endorsed_tcb)); if (uvm_endorsements.has_value()) { parsed_uvm_endorsements = diff --git a/src/node/node_state.h b/src/node/node_state.h index 858cd0224db7..6a405b6d406f 100644 --- a/src/node/node_state.h +++ b/src/node/node_state.h @@ -881,7 +881,7 @@ namespace ccf } auto snp_attestation = - AttestationProvider::get_snp_attestation(quote_info); + AttestationProvider::get_snp_attestation_report(quote_info); if (snp_attestation.has_value()) { snp_tcb_version = snp_attestation.value().reported_tcb(); @@ -1033,7 +1033,7 @@ namespace ccf // Check that tcbm in endorsement matches reported TCB in our // retrieved attestation const auto report = - ccf::pal::snp::parse_attestation_report_unverified( + ccf::pal::snp::AttestationReport::from_unverified( quote_info.quote); const auto reported_tcb = report.reported_tcb(); diff --git a/src/node/quote.cpp b/src/node/quote.cpp index 0f389bf5aa12..91dc105201e8 100644 --- a/src/node/quote.cpp +++ b/src/node/quote.cpp @@ -21,6 +21,8 @@ #include "node/js_policy.h" #include "node/uvm_endorsements.h" +#include + namespace ccf { bool verify_enclave_measurement_against_uvm_endorsements( @@ -151,7 +153,7 @@ namespace ccf } std::optional AttestationProvider:: - get_snp_attestation(const QuoteInfo& quote_info) + get_snp_attestation_report(const QuoteInfo& quote_info) { if (quote_info.format != QuoteFormat::amd_sev_snp_v1) { @@ -161,7 +163,12 @@ namespace ccf { pal::PlatformAttestationMeasurement d = {}; pal::PlatformAttestationReportData r = {}; - return pal::verify_snp_attestation_report_and_get(quote_info, d, r); + return pal::snp::AttestationReport::verify( + quote_info.quote, + quote_info.endorsements, + d, + r, + quote_info.endorsed_tcb); } catch (const std::exception& e) { @@ -170,6 +177,33 @@ namespace ccf } } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + std::optional AttestationProvider::get_snp_attestation( + const QuoteInfo& quote_info) + { + auto report = get_snp_attestation_report(quote_info); + if (!report.has_value()) + { + return std::nullopt; + } + + if (quote_info.quote.size() != sizeof(pal::snp::Attestation)) + { + LOG_FAIL_FMT( + "Verified SNP report has unexpected size {} (expected {})", + quote_info.quote.size(), + sizeof(pal::snp::Attestation)); + return std::nullopt; + } + + pal::snp::Attestation legacy_report = {}; + std::memcpy( + &legacy_report, quote_info.quote.data(), sizeof(pal::snp::Attestation)); + return legacy_report; + } +#pragma GCC diagnostic pop + std::optional AttestationProvider::get_host_data( const QuoteInfo& quote_info) { @@ -199,8 +233,12 @@ namespace ccf pal::PlatformAttestationReportData r = {}; try { - const auto report = - pal::verify_snp_attestation_report_and_get(quote_info, d, r); + const auto report = pal::snp::AttestationReport::verify( + quote_info.quote, + quote_info.endorsements, + d, + r, + quote_info.endorsed_tcb); const auto host_data = report.host_data(); std::copy(host_data.begin(), host_data.end(), rep.begin()); } @@ -270,8 +308,8 @@ namespace ccf pal::PlatformAttestationMeasurement d = {}; pal::PlatformAttestationReportData r = {}; - auto attestation = - pal::verify_snp_attestation_report_and_get(quote_info, d, r); + auto attestation = pal::snp::AttestationReport::verify( + quote_info.quote, quote_info.endorsements, d, r, quote_info.endorsed_tcb); std::optional min_tcb_opt = std::nullopt; auto* h = tx.ro(Tables::SNP_TCB_VERSIONS); diff --git a/src/node/rpc/node_frontend.h b/src/node/rpc/node_frontend.h index bd466d8177b5..62b16ddc7e14 100644 --- a/src/node/rpc/node_frontend.h +++ b/src/node/rpc/node_frontend.h @@ -1662,7 +1662,8 @@ namespace ccf ctx.tx, in.snp_uvm_endorsements, recovering); auto attestation = - AttestationProvider::get_snp_attestation(in.quote_info).value(); + AttestationProvider::get_snp_attestation_report(in.quote_info) + .value(); InternalTablesAccess::trust_node_snp_tcb_version( ctx.tx, attestation); break; diff --git a/src/pal/attestation.cpp b/src/pal/attestation.cpp index f0376e466b38..1d818011c533 100644 --- a/src/pal/attestation.cpp +++ b/src/pal/attestation.cpp @@ -27,16 +27,6 @@ namespace ccf::pal {} }; - class AttestationReportFactory - { - public: - static AttestationReport make(TavAttestationReportPtr&& report) - { - return AttestationReport( - std::make_unique(std::move(report))); - } - }; - AttestationReport::AttestationReport(std::unique_ptr impl_) : impl(std::move(impl_)) {} @@ -51,7 +41,7 @@ namespace ccf::pal using BytesAccessor = void (*)(const TavSnpAttestationReport*, const uint8_t**, size_t*); - std::vector get_bytes( + std::span get_bytes( const TavSnpAttestationReport* report, BytesAccessor accessor, size_t expected_size, @@ -69,7 +59,7 @@ namespace ccf::pal data == nullptr ? "is null" : "is not null", expected_size)); } - return {data, data + size}; + return {data, size}; } [[noreturn]] void throw_tav_error( @@ -142,7 +132,7 @@ namespace ccf::pal #undef SNP_SCALAR_ACCESSOR #define SNP_BYTES_ACCESSOR(method, tav_accessor, size) \ - std::vector AttestationReport::method() const \ + std::span AttestationReport::method() const \ { \ return get_bytes(impl->report.get(), tav_accessor, size, #method); \ } @@ -173,7 +163,7 @@ namespace ccf::pal TcbVersionRaw AttestationReport::platform_version() const { - return TcbVersionRaw(get_bytes( + return TcbVersionRaw::from_span(get_bytes( impl->report.get(), tav_snp_attestation_report_platform_version, snp_tcb_version_size, @@ -182,7 +172,7 @@ namespace ccf::pal TcbVersionRaw AttestationReport::reported_tcb() const { - return TcbVersionRaw(get_bytes( + return TcbVersionRaw::from_span(get_bytes( impl->report.get(), tav_snp_attestation_report_reported_tcb, snp_tcb_version_size, @@ -191,7 +181,7 @@ namespace ccf::pal TcbVersionRaw AttestationReport::committed_tcb() const { - return TcbVersionRaw(get_bytes( + return TcbVersionRaw::from_span(get_bytes( impl->report.get(), tav_snp_attestation_report_committed_tcb, snp_tcb_version_size, @@ -200,14 +190,14 @@ namespace ccf::pal TcbVersionRaw AttestationReport::launch_tcb() const { - return TcbVersionRaw(get_bytes( + return TcbVersionRaw::from_span(get_bytes( impl->report.get(), tav_snp_attestation_report_launch_tcb, snp_tcb_version_size, "launch_tcb")); } - std::vector AttestationReport::chip_id_for_vcek() const + std::span AttestationReport::chip_id_for_vcek() const { auto id = chip_id(); const auto product = get_sev_snp_product(cpuid_fam_id(), cpuid_mod_id()); @@ -217,14 +207,13 @@ namespace ccf::pal } if (product == ProductName::Turin) { - id.resize(8); - return id; + return id.first(8); } throw std::logic_error( fmt::format("Unsupported SEV-SNP product: {}", product)); } - AttestationReport parse_attestation_report_unverified( + AttestationReport AttestationReport::from_unverified( std::span report) { TavSnpAttestationReport* raw_report = nullptr; @@ -240,7 +229,14 @@ namespace ccf::pal throw std::logic_error( "SEV-SNP: TAV parsing succeeded without returning a report"); } - return AttestationReportFactory::make(std::move(parsed_report)); + return AttestationReport( + std::make_unique(std::move(parsed_report))); + } + + AttestationReport parse_attestation_report_unverified( + std::span report) + { + return AttestationReport::from_unverified(report); } } @@ -437,23 +433,25 @@ namespace ccf::pal } // Verifying SNP attestation report is available on all platforms. - snp::AttestationReport verify_snp_attestation_report_and_get( - const QuoteInfo& quote_info, + snp::AttestationReport snp::AttestationReport::verify( + std::span report, + std::span endorsements, PlatformAttestationMeasurement& measurement, - PlatformAttestationReportData& report_data) + PlatformAttestationReportData& report_data, + std::optional claimed_endorsed_tcb) { - if (quote_info.format != QuoteFormat::amd_sev_snp_v1) + if (report.size() != snp::attestation_report_size) { throw std::logic_error(fmt::format( - "Unexpected attestation report to verify for SEV-SNP: {}", - quote_info.format)); + "Input SEV-SNP attestation report is not of expected size {}: {}", + snp::attestation_report_size, + report.size())); } // ---- Verify certificate chain ---- auto certificates = ccf::crypto::split_x509_cert_bundle(std::string_view( - reinterpret_cast(quote_info.endorsements.data()), - quote_info.endorsements.size())); + reinterpret_cast(endorsements.data()), endorsements.size())); if (certificates.size() != 3) { throw std::logic_error(fmt::format( @@ -468,8 +466,8 @@ namespace ccf::pal TavSnpAttestationReport* verified_report_raw = nullptr; TavErrorPtr verification_error(tav_verify_snp_attestation( - quote_info.quote.data(), - quote_info.quote.size(), + report.data(), + report.size(), ark_cert.data(), ark_cert.size(), ask_cert.data(), @@ -495,7 +493,7 @@ namespace ccf::pal } auto attestation = - snp::AttestationReportFactory::make(std::move(verified_report)); + AttestationReport(std::make_unique(std::move(verified_report))); if (attestation.version() < snp::minimum_attestation_version) { @@ -586,10 +584,10 @@ namespace ccf::pal ccf::ds::to_hex(reported_chip_id))); } - if (quote_info.endorsed_tcb.has_value()) + if (claimed_endorsed_tcb.has_value()) { - const auto& quote_endorsed_tcb = quote_info.endorsed_tcb.value(); - auto raw_endorsed_tcb = snp::TcbVersionRaw::from_hex(quote_endorsed_tcb); + auto raw_endorsed_tcb = + snp::TcbVersionRaw::from_hex(std::string(claimed_endorsed_tcb.value())); const auto reported_tcb = attestation.reported_tcb(); if (raw_endorsed_tcb != reported_tcb) @@ -610,12 +608,44 @@ namespace ccf::pal return attestation; } + snp::AttestationReport verify_snp_attestation_report_and_get( + const QuoteInfo& quote_info, + PlatformAttestationMeasurement& measurement, + PlatformAttestationReportData& report_data) + { + if (quote_info.format != QuoteFormat::amd_sev_snp_v1) + { + throw std::logic_error(fmt::format( + "Unexpected attestation report to verify for SEV-SNP: {}", + quote_info.format)); + } + + return snp::AttestationReport::verify( + quote_info.quote, + quote_info.endorsements, + measurement, + report_data, + quote_info.endorsed_tcb); + } + void verify_snp_attestation_report( const QuoteInfo& quote_info, PlatformAttestationMeasurement& measurement, PlatformAttestationReportData& report_data) { - verify_snp_attestation_report_and_get(quote_info, measurement, report_data); + if (quote_info.format != QuoteFormat::amd_sev_snp_v1) + { + throw std::logic_error(fmt::format( + "Unexpected attestation report to verify for SEV-SNP: {}", + quote_info.format)); + } + + snp::AttestationReport::verify( + quote_info.quote, + quote_info.endorsements, + measurement, + report_data, + quote_info.endorsed_tcb); } void verify_quote( diff --git a/src/pal/quote_generation.h b/src/pal/quote_generation.h index 676c88eefd0c..d66d8b33805a 100644 --- a/src/pal/quote_generation.h +++ b/src/pal/quote_generation.h @@ -89,10 +89,9 @@ namespace ccf::pal { QuoteInfo node_quote_info = {}; node_quote_info.format = QuoteFormat::amd_sev_snp_v1; - auto attestation = snp::get_attestation(report_data); - node_quote_info.quote = attestation->get_raw(); + node_quote_info.quote = snp::get_attestation_bytes(report_data); auto report = - snp::parse_attestation_report_unverified(node_quote_info.quote); + snp::AttestationReport::from_unverified(node_quote_info.quote); if (report.version() < pal::snp::minimum_attestation_version) { diff --git a/src/pal/test/snp_attestation_validation.cpp b/src/pal/test/snp_attestation_validation.cpp index 51db80da74f1..3a36ba0b7035 100644 --- a/src/pal/test/snp_attestation_validation.cpp +++ b/src/pal/test/snp_attestation_validation.cpp @@ -6,12 +6,14 @@ #include "ccf/ds/hex.h" #include "ccf/ds/logger.h" #include "ccf/ds/quote_info.h" +#include "ccf/node/quote.h" #include "ccf/pal/attestation.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" #include "ccf/pal/sev_snp_cpuid.h" +#include "ccf/pal/snp_ioctl.h" #include "crypto/openssl/hash.h" #include "pal/test/attestation.h" #include "pal/test/attestation_sev_snp_endorsements.h" @@ -19,7 +21,9 @@ #include #include +#include #include +#include #define DOCTEST_CONFIG_IMPLEMENT #include @@ -132,8 +136,16 @@ TEST_CASE("unverified SNP report accessors") { using namespace ccf::pal; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + static_assert(std::is_same_v< + decltype(snp::parse_attestation_report_unverified( + std::declval>())), + snp::AttestationReport>); +#pragma GCC diagnostic pop + auto report = - snp::parse_attestation_report_unverified(snp::testing::milan_attestation); + snp::AttestationReport::from_unverified(snp::testing::milan_attestation); CHECK(report.version() == 3); CHECK(report.cpuid_fam_id() == 25); @@ -148,7 +160,7 @@ TEST_CASE("unverified SNP report accessors") TEST_CASE("unverified SNP report rejects invalid sizes") { CHECK_THROWS_WITH_AS( - ccf::pal::snp::parse_attestation_report_unverified( + ccf::pal::snp::AttestationReport::from_unverified( std::vector(100)), doctest::Contains( "SEV-SNP: TAV unverified report parsing failed (1): Invalid " @@ -156,6 +168,228 @@ TEST_CASE("unverified SNP report rejects invalid sizes") std::logic_error); } +TEST_CASE("SNP byte accessors borrow report storage") +{ + using Report = ccf::pal::snp::AttestationReport; + struct ByteField + { + std::span (Report::*accessor)() const; + size_t offset; + size_t size; + }; + const ByteField fields[] = { + {&Report::family_id, 0x010, 16}, + {&Report::image_id, 0x020, 16}, + {&Report::report_data, 0x050, 64}, + {&Report::measurement, 0x090, 48}, + {&Report::host_data, 0x0C0, 32}, + {&Report::id_key_digest, 0x0E0, 48}, + {&Report::author_key_digest, 0x110, 48}, + {&Report::report_id, 0x140, 32}, + {&Report::report_id_ma, 0x160, 32}, + {&Report::chip_id, 0x1A0, 64}, + {&Report::chip_id_for_vcek, 0x1A0, 64}, + {&Report::signature_r, 0x2A0, 72}, + {&Report::signature_s, 0x2E8, 72}}; + + const auto& raw_report = ccf::pal::snp::testing::milan_attestation; + auto report = Report::from_unverified(raw_report); + for (const auto& [accessor, offset, size] : fields) + { + const auto first = (report.*accessor)(); + const auto second = (report.*accessor)(); + CHECK(first.data() == second.data()); + REQUIRE(first.size() == size); + CHECK(std::equal(first.begin(), first.end(), raw_report.begin() + offset)); + } +} + +TEST_CASE("SNP borrowed bytes survive report ownership transfers") +{ + using namespace ccf::pal::snp; + auto raw_report = testing::milan_attestation; + std::optional original = + AttestationReport::from_unverified(raw_report); + const auto measurement = original->measurement(); + raw_report[0x090] ^= 0xff; + CHECK(measurement[0] == testing::milan_attestation[0x090]); + + auto moved = std::move(*original); + original.reset(); + CHECK(measurement.data() == moved.measurement().data()); + + auto assigned = + AttestationReport::from_unverified(testing::genoa_attestation); + assigned = std::move(moved); + CHECK(measurement.data() == assigned.measurement().data()); + CHECK(std::equal( + measurement.begin(), + measurement.end(), + testing::milan_attestation.begin() + 0x090)); +} + +TEST_CASE("VCEK chip ID borrows the product-specific prefix") +{ + using namespace ccf::pal::snp; + for (const auto* raw_report : + {&testing::milan_attestation, + &testing::genoa_attestation, + &testing::turin_attestation}) + { + auto report = AttestationReport::from_unverified(*raw_report); + const auto chip_id = report.chip_id(); + const auto vcek_chip_id = report.chip_id_for_vcek(); + CHECK(vcek_chip_id.data() == chip_id.data()); + CHECK( + vcek_chip_id.size() == + (get_sev_snp_product(report.cpuid_fam_id(), report.cpuid_mod_id()) == + ProductName::Turin ? + 8 : + 64)); + } +} + +TEST_CASE("TCB values can be constructed from borrowed bytes") +{ + using ccf::pal::snp::TcbVersionRaw; + std::array bytes = {4, 0, 0, 0, 0, 0, 24, 219}; + const auto tcb = TcbVersionRaw::from_span(bytes); + CHECK(tcb.to_hex() == "db18000000000004"); + CHECK(tcb == TcbVersionRaw(std::vector(bytes.begin(), bytes.end()))); + bytes.fill(0); + CHECK(tcb.to_hex() == "db18000000000004"); + CHECK_THROWS_WITH_AS( + TcbVersionRaw::from_span(std::span(bytes).first(7)), + "Invalid TCB version raw data size: 7", + std::logic_error); +} + +TEST_CASE("SNP verification preserves invalid size error") +{ + ccf::pal::PlatformAttestationMeasurement measurement; + ccf::pal::PlatformAttestationReportData report_data; + CHECK_THROWS_WITH_AS( + ccf::pal::snp::AttestationReport::verify( + std::vector(100), {}, measurement, report_data), + doctest::Contains( + "Input SEV-SNP attestation report is not of expected size 1184: 100"), + std::logic_error); +} + +TEST_CASE("SNP ioctl response bytes exclude response headers and padding") +{ + using namespace ccf::pal::snp; + static_assert( + std::is_same_v< + decltype(get_attestation_bytes( + std::declval())), + std::vector>); + + ioctl6::IoctlSentinel response; + response.data.fill(0xa5); + const uint32_t report_size = attestation_report_size; + std::memcpy( + response.data.data() + ioctl6::detail::REPORT_SIZE_OFFSET, + &report_size, + sizeof(report_size)); + std::copy( + testing::milan_attestation.begin(), + testing::milan_attestation.end(), + response.data.begin() + ioctl6::detail::REPORT_OFFSET); + + const auto report_bytes = + ioctl6::detail::extract_attestation_bytes(response.data); + CHECK(report_bytes == testing::milan_attestation); + CHECK(response.sentinels_intact()); + response.data.fill(0); + CHECK(report_bytes == testing::milan_attestation); + + response.post_sentinels[1] ^= 1; + CHECK_FALSE(response.sentinels_intact()); +} + +TEST_CASE("SNP ioctl response bytes reject invalid report sizes") +{ + using namespace ccf::pal::snp; + ioctl6::detail::AttestationResponseBytes response = {}; + for (const uint32_t report_size : + {0U, 1183U, 1185U, std::numeric_limits::max()}) + { + std::memcpy( + response.data() + ioctl6::detail::REPORT_SIZE_OFFSET, + &report_size, + sizeof(report_size)); + const auto expected_error = fmt::format( + "Unexpected SEV-SNP attestation report size: {} != {}", + report_size, + attestation_report_size); + CHECK_THROWS_WITH_AS( + ioctl6::detail::extract_attestation_bytes(response), + expected_error.c_str(), + std::logic_error); + } +} + +TEST_CASE("SNP byte acquisition rejects oversized report data before ioctl") +{ + ccf::pal::PlatformAttestationReportData report_data; + report_data.data.resize(ccf::pal::snp_attestation_report_data_size + 1); + CHECK_THROWS_WITH_AS( + ccf::pal::snp::ioctl6::get_attestation_bytes(report_data), + "User-defined report data is larger than available space", + std::logic_error); +} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" +TEST_CASE("legacy SNP report layout remains compatible") +{ + using ccf::pal::snp::Attestation; + + static_assert( + std::is_same_v< + decltype(std::declval().get()), + const Attestation&>); + static_assert( + std::is_same_v< + decltype(std::declval().get_raw()), + std::vector>); + static_assert( + std::is_same_v< + decltype(std::declval().get_raw()), + std::vector>); + static_assert(std::is_same_v< + decltype(ccf::AttestationProvider::get_snp_attestation( + std::declval())), + std::optional>); + static_assert(std::is_same_v< + decltype(ccf::pal::snp::ioctl6::AttestationResp::report), + Attestation>); + + CHECK(ccf::pal::snp::amd_root_signing_keys.size() == 3); + CHECK( + ccf::pal::snp::amd_root_signing_keys.at(ccf::pal::snp::ProductName::Milan) + .public_key == ccf::pal::snp::amd_milan_root_signing_public_key); + + Attestation report = {}; + CHECK(sizeof(report) == ccf::pal::snp::attestation_report_size); + CHECK(offsetof(Attestation, version) == 0x000); + CHECK(offsetof(Attestation, policy) == 0x008); + CHECK(offsetof(Attestation, report_data) == 0x050); + CHECK(offsetof(Attestation, measurement) == 0x090); + CHECK(offsetof(Attestation, reported_tcb) == 0x180); + CHECK(offsetof(Attestation, chip_id) == 0x1A0); + CHECK(offsetof(Attestation, signature) == 0x2A0); + + report.version = ccf::pal::snp::minimum_attestation_version; + report.cpuid_fam_id = 0x19; + report.cpuid_mod_id = 0x01; + const auto config = + ccf::pal::snp::make_endorsement_endpoint_configuration(report); + CHECK(config.servers.size() == 1); +} +#pragma clang diagnostic pop + TEST_CASE("milan validation") { using namespace ccf; @@ -172,6 +406,16 @@ TEST_CASE("milan validation") pal::PlatformAttestationMeasurement measurement; pal::PlatformAttestationReportData report_data; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + static_assert(std::is_same_v< + decltype(pal::verify_snp_attestation_report_and_get( + std::declval(), + std::declval(), + std::declval())), + pal::snp::AttestationReport>); +#pragma GCC diagnostic pop + pal::verify_snp_attestation_report( milan_quote_info, measurement, report_data); } @@ -318,7 +562,7 @@ TEST_CASE("Parsing of Tcb versions from strings") TEST_CASE("Parsing tcb versions from attestaion") { - auto milan_attestation = ccf::pal::snp::parse_attestation_report_unverified( + auto milan_attestation = ccf::pal::snp::AttestationReport::from_unverified( ccf::pal::snp::testing::milan_attestation); auto milan_tcb = milan_attestation.reported_tcb() .to_policy(ccf::pal::snp::ProductName::Milan) @@ -536,8 +780,7 @@ TEST_CASE("Quote endorsements url generation") for (auto [attestation, servers, expected_url] : test_cases) { - auto quote = - ccf::pal::snp::parse_attestation_report_unverified(attestation); + auto quote = ccf::pal::snp::AttestationReport::from_unverified(attestation); auto config = ccf::pal::snp::make_endorsement_endpoint_configuration(quote, servers); @@ -548,7 +791,7 @@ TEST_CASE("Quote endorsements url generation") TEST_CASE("Quote endorsements generation for v2 attestation version fails") { auto v2_format_milan_attestation = - ccf::pal::snp::parse_attestation_report_unverified( + ccf::pal::snp::AttestationReport::from_unverified( ccf::pal::snp::testing::v2_format_milan_attestation); CHECK_EQ(v2_format_milan_attestation.version(), 2); @@ -580,7 +823,7 @@ TEST_CASE("Extracting metadata from endorsements") }; auto attestation = - pal::snp::parse_attestation_report_unverified(milan_quote_info.quote); + pal::snp::AttestationReport::from_unverified(milan_quote_info.quote); auto certificates = ccf::crypto::split_x509_cert_bundle(std::string_view( reinterpret_cast(milan_quote_info.endorsements.data()), diff --git a/src/pal/test/snp_ioctl_test.cpp b/src/pal/test/snp_ioctl_test.cpp index 418fbf810c45..01d2595198b5 100644 --- a/src/pal/test/snp_ioctl_test.cpp +++ b/src/pal/test/snp_ioctl_test.cpp @@ -23,10 +23,10 @@ TEST_CASE("SNP request attestation") snp_report_data.report_data.begin(), snp_report_data.report_data.end(), 0); PlatformAttestationReportData report_data(snp_report_data); - snp::ioctl6::Attestation ioctl_attestation(report_data); - + const auto report_bytes = snp::get_attestation_bytes(report_data); + REQUIRE(report_bytes.size() == snp::attestation_report_size); const auto attestation = - snp::parse_attestation_report_unverified(ioctl_attestation.get_raw()); + snp::AttestationReport::from_unverified(report_bytes); SnpAttestationReportData attested_report_data(attestation.report_data()); diff --git a/src/pal/test/verify_attestation.cpp b/src/pal/test/verify_attestation.cpp index 115482e8a075..ae9893b08c97 100644 --- a/src/pal/test/verify_attestation.cpp +++ b/src/pal/test/verify_attestation.cpp @@ -19,7 +19,7 @@ void fetch_endorsements( const std::vector& attestation_raw, std::vector& output) { auto attestation = - ccf::pal::snp::parse_attestation_report_unverified(attestation_raw); + ccf::pal::snp::AttestationReport::from_unverified(attestation_raw); auto endorsement_config = ccf::pal::snp::make_endorsement_endpoint_configuration( @@ -66,7 +66,7 @@ int main(int argc, char** argv) ->check([](const std::string& attestation_hex) { try { - ccf::pal::snp::parse_attestation_report_unverified( + ccf::pal::snp::AttestationReport::from_unverified( ccf::ds::from_hex(attestation_hex)); return std::string(); } diff --git a/src/pal/test/verify_uvm_attestation_and_endorsements.cpp b/src/pal/test/verify_uvm_attestation_and_endorsements.cpp index 86f1619113a7..bd8f694bef46 100644 --- a/src/pal/test/verify_uvm_attestation_and_endorsements.cpp +++ b/src/pal/test/verify_uvm_attestation_and_endorsements.cpp @@ -242,7 +242,7 @@ int main(int argc, char** argv) LOG_INFO_FMT("Verifying endorsements"); const auto attestation_unverified = - ccf::pal::snp::parse_attestation_report_unverified(quote_info.quote); + ccf::pal::snp::AttestationReport::from_unverified(quote_info.quote); validate_endorsements( endorsements, attestation_unverified.reported_tcb(), diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 925ef8912222..b2f658951d1f 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "bitflags" -version = "2.13.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "bumpalo" @@ -26,9 +26,9 @@ source = "git+https://github.com/project-everest/everparse.git?rev=950bc93838ac2 [[package]] name = "cc" -version = "1.2.56" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e" dependencies = [ "find-msvc-tools", "shlex", @@ -66,9 +66,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de" [[package]] name = "foreign-types" @@ -87,21 +87,21 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-core", "futures-task", @@ -117,9 +117,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.103" +version = "0.3.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" dependencies = [ "cfg-if", "futures-util", @@ -128,9 +128,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.183" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "log" @@ -172,7 +172,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -195,24 +195,24 @@ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.46" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -225,38 +225,38 @@ checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", ] [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.3", ] [[package]] name = "serde_json" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", @@ -267,9 +267,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "slab" @@ -288,9 +288,20 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "tee-attestation-verification-caci" -version = "1.0.7" +version = "1.0.8" dependencies = [ "serde_json", "tee-attestation-verification-cose", @@ -299,19 +310,26 @@ dependencies = [ ] [[package]] -name = "tee-attestation-verification-cose" -version = "1.0.7" +name = "tee-attestation-verification-cbor" +version = "1.0.8" dependencies = [ "cborrs", "cborrs-nondet", +] + +[[package]] +name = "tee-attestation-verification-cose" +version = "1.0.8" +dependencies = [ + "tee-attestation-verification-cbor", "tee-attestation-verification-crypto", ] [[package]] name = "tee-attestation-verification-crypto" -version = "1.0.7" +version = "1.0.8" dependencies = [ - "foreign-types", + "foreign-types-shared", "js-sys", "openssl", "openssl-sys", @@ -321,11 +339,13 @@ dependencies = [ [[package]] name = "tee-attestation-verification-ffi" -version = "1.0.7" +version = "1.0.8" dependencies = [ "js-sys", + "serde", "serde_json", "tee-attestation-verification-caci", + "tee-attestation-verification-cbor", "tee-attestation-verification-cose", "tee-attestation-verification-crypto", "tee-attestation-verification-lib", @@ -336,7 +356,7 @@ dependencies = [ [[package]] name = "tee-attestation-verification-lib" -version = "1.0.7" +version = "1.0.8" dependencies = [ "log", "tee-attestation-verification-crypto", @@ -357,9 +377,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "wasm-bindgen" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" dependencies = [ "cfg-if", "once_cell", @@ -370,9 +390,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.76" +version = "0.4.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" dependencies = [ "js-sys", "wasm-bindgen", @@ -380,9 +400,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -390,44 +410,44 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.119", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" dependencies = [ "unicode-ident", ] [[package]] name = "zerocopy" -version = "0.8.54" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.54" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] diff --git a/src/rust/test/native_link.c b/src/rust/test/native_link.c new file mode 100644 index 000000000000..97eeb2384f8e --- /dev/null +++ b/src/rust/test/native_link.c @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the Apache 2.0 License. + +#include "tav/snp.h" + +#include + +int main(void) +{ + const uint8_t bytes[1184] = {0}; + TavSnpAttestationReport* report = NULL; + TavError* error = tav_snp_attestation_report_from_unverified_bytes( + bytes, sizeof(bytes), &report); + if (error != NULL) + { + fprintf(stderr, "TAV parsing failed: %s\n", tav_error_message(error)); + tav_error_free(error); + tav_snp_attestation_report_free(report); + return 1; + } + if (report == NULL) + { + fputs("TAV parsing returned no report\n", stderr); + return 1; + } + const uint32_t version = tav_snp_attestation_report_version(report); + tav_snp_attestation_report_free(report); + if (version != 0) + { + fputs("TAV parsing returned an unexpected report version\n", stderr); + return 1; + } + return 0; +} From 02409fbfb51b633940566c9898dbf8c7718bfeb3 Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Wed, 9 Sep 2026 17:41:06 +0100 Subject: [PATCH 05/14] Use TAV C accessors directly for SNP reports Replace the report accessor class and pimpl with an owning smart-pointer alias. Keep parsing and CCF verification in free functions, borrow byte fields through a shared helper, and expose the existing C ABI headers without adding a C++ wrapper. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e97cc978-adf6-4072-bd60-c1d9b51096d8 --- CHANGELOG.md | 2 +- CMakeLists.txt | 5 + include/ccf/pal/attestation.h | 13 +- include/ccf/pal/attestation_sev_snp.h | 150 ++++----- include/ccf/pal/snp_ioctl6.h | 2 +- src/js/extensions/snp_attestation.cpp | 163 +++++---- src/node/node_state.h | 11 +- src/node/quote.cpp | 26 +- src/pal/attestation.cpp | 310 ++++-------------- src/pal/quote_generation.h | 8 +- src/pal/tav_ffi.h | 28 -- src/pal/test/snp_attestation_validation.cpp | 198 +++++++---- src/pal/test/snp_ioctl_test.cpp | 5 +- src/pal/test/verify_attestation.cpp | 6 +- ...erify_uvm_attestation_and_endorsements.cpp | 7 +- src/service/internal_tables_access.h | 28 +- 16 files changed, 458 insertions(+), 504 deletions(-) delete mode 100644 src/pal/tav_ffi.h diff --git a/CHANGELOG.md b/CHANGELOG.md index c15ee6305143..8f3dce7bada9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed -- SNP attestation reports are now parsed and verified through the move-only, TAV-backed `ccf::pal::snp::AttestationReport` accessor API. Byte accessors return read-only spans into the report; moves preserve these views, but destroying or replacing the owning report invalidates them. The packed `ccf::pal::snp::Attestation` wire-layout type and its legacy accessors remain available for compatibility but are deprecated. (#8083) +- SNP attestation reports now use TAV's C API with `ccf::pal::snp::AttestationReport` as an owning smart-pointer alias. Use `parse_attestation_report_unverified()` to decode reports and `verify_attestation_report()` to apply TAV verification and CCF's policy. TAV byte accessors borrow report storage; destroying or replacing the owner invalidates those views. The packed `ccf::pal::snp::Attestation` wire-layout type and its legacy accessors remain available but are deprecated. (#8083) - `ccf::pal::snp::get_attestation_bytes()` in `ccf/pal/snp_ioctl.h` requests an unverified SNP report as owned bytes, without using the legacy report type. `AttestationInterface::get_raw()` and its ioctl implementation remain available but are deprecated. (#8083) - CBOR parsing now rejects composite (array or map) and tagged values used as map keys anywhere in the decoded document, including nested maps in optional COSE headers (#8297). diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f80432bcdf4..32c816a8492d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -513,6 +513,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/ diff --git a/include/ccf/pal/attestation.h b/include/ccf/pal/attestation.h index 452b3ed7bf0f..34eeb7306111 100644 --- a/include/ccf/pal/attestation.h +++ b/include/ccf/pal/attestation.h @@ -29,7 +29,18 @@ namespace ccf::pal PlatformAttestationMeasurement& measurement, PlatformAttestationReportData& report_data); - [[deprecated("Use snp::AttestationReport::verify")]] + namespace snp + { + /// Verify with TAV, then enforce CCF's SNP attestation policy. + AttestationReport verify_attestation_report( + std::span report, + std::span endorsements, + PlatformAttestationMeasurement& measurement, + PlatformAttestationReportData& report_data, + std::optional endorsed_tcb = std::nullopt); + } + + [[deprecated("Use snp::verify_attestation_report")]] snp::AttestationReport verify_snp_attestation_report_and_get( const QuoteInfo& quote_info, PlatformAttestationMeasurement& measurement, diff --git a/include/ccf/pal/attestation_sev_snp.h b/include/ccf/pal/attestation_sev_snp.h index 96b4823cdcdd..541d2e023de3 100644 --- a/include/ccf/pal/attestation_sev_snp.h +++ b/include/ccf/pal/attestation_sev_snp.h @@ -21,6 +21,7 @@ #include #include #include +#include #include namespace ccf::pal::snp @@ -441,81 +442,55 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== }; #pragma pack(pop) - class AttestationReport + struct AttestationReportDeleter { - private: - class Impl; - std::unique_ptr impl; + void operator()(TavSnpAttestationReport* report) const noexcept + { + tav_snp_attestation_report_free(report); + } + }; - explicit AttestationReport(std::unique_ptr impl_); + using AttestationReport = + std::unique_ptr; - public: - static AttestationReport from_unverified(std::span report); - static AttestationReport verify( - std::span report, - std::span endorsements, - PlatformAttestationMeasurement& measurement, - PlatformAttestationReportData& report_data, - std::optional endorsed_tcb = std::nullopt); - - // Do not call accessors on a moved-from report. - AttestationReport(AttestationReport&&) noexcept; - AttestationReport& operator=(AttestationReport&&) noexcept; - ~AttestationReport(); - - AttestationReport(const AttestationReport&) = delete; - AttestationReport& operator=(const AttestationReport&) = delete; - - [[nodiscard]] uint32_t version() const; - [[nodiscard]] uint32_t guest_svn() const; - [[nodiscard]] uint64_t policy() const; - [[nodiscard]] uint8_t policy_abi_minor() const; - [[nodiscard]] uint8_t policy_abi_major() const; - [[nodiscard]] bool policy_smt() const; - [[nodiscard]] bool policy_migrate_ma() const; - [[nodiscard]] bool policy_debug() const; - [[nodiscard]] bool policy_single_socket() const; - [[nodiscard]] uint32_t vmpl() const; - [[nodiscard]] uint32_t signature_algo() const; - [[nodiscard]] uint64_t platform_info() const; - [[nodiscard]] uint32_t flags() const; - [[nodiscard]] bool flags_author_key_en() const; - [[nodiscard]] bool flags_mask_chip_key() const; - [[nodiscard]] uint8_t flags_signing_key() const; - [[nodiscard]] uint8_t cpuid_fam_id() const; - [[nodiscard]] uint8_t cpuid_mod_id() const; - [[nodiscard]] uint8_t cpuid_step() const; - [[nodiscard]] uint8_t current_build() const; - [[nodiscard]] uint8_t current_minor() const; - [[nodiscard]] uint8_t current_major() const; - [[nodiscard]] uint8_t committed_build() const; - [[nodiscard]] uint8_t committed_minor() const; - [[nodiscard]] uint8_t committed_major() const; - - // Byte spans borrow report storage. Moves transfer ownership without - // invalidating these views. Destroying or replacing the owner invalidates - // them. - [[nodiscard]] std::span family_id() const; - [[nodiscard]] std::span image_id() const; - [[nodiscard]] TcbVersionRaw platform_version() const; - [[nodiscard]] std::span report_data() const; - [[nodiscard]] std::span measurement() const; - [[nodiscard]] std::span host_data() const; - [[nodiscard]] std::span id_key_digest() const; - [[nodiscard]] std::span author_key_digest() const; - [[nodiscard]] std::span report_id() const; - [[nodiscard]] std::span report_id_ma() const; - [[nodiscard]] TcbVersionRaw reported_tcb() const; - [[nodiscard]] std::span chip_id() const; - [[nodiscard]] std::span chip_id_for_vcek() const; - [[nodiscard]] TcbVersionRaw committed_tcb() const; - [[nodiscard]] TcbVersionRaw launch_tcb() const; - [[nodiscard]] std::span signature_r() const; - [[nodiscard]] std::span signature_s() const; - }; + using ReportBytesAccessor = + void (*)(const TavSnpAttestationReport*, const uint8_t**, size_t*); - [[deprecated("Use AttestationReport::from_unverified")]] - AttestationReport parse_attestation_report_unverified( + // The returned view borrows storage owned by the report handle. + inline std::span get_report_bytes( + const TavSnpAttestationReport* report, ReportBytesAccessor accessor) + { + if (report == nullptr) + { + throw std::logic_error("Cannot access an empty SNP attestation report"); + } + const uint8_t* data = nullptr; + size_t size = 0; + accessor(report, &data, &size); + return {data, size}; + } + + inline std::span get_chip_id_for_vcek( + const AttestationReport& report) + { + const auto chip_id = + get_report_bytes(report.get(), tav_snp_attestation_report_chip_id); + 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 report); inline void to_json(nlohmann::json& j, const TcbVersionRaw& tcb_version) @@ -563,20 +538,23 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== const AttestationReport& quote, const snp::EndorsementsServers& endorsements_servers = {}) { - if (quote.version() < minimum_attestation_version) + 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.chip_id_for_vcek(), "")); - const auto reported_tcb_raw = quote.reported_tcb().data(); + fmt::format("{:02x}", fmt::join(get_chip_id_for_vcek(quote), "")); + const auto reported_tcb_raw = + get_report_bytes(quote.get(), tav_snp_attestation_report_reported_tcb); uint64_t reported_tcb_value = 0; std::memcpy( &reported_tcb_value, reported_tcb_raw.data(), sizeof(reported_tcb_value)); @@ -621,8 +599,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; @@ -635,7 +614,11 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== case ProductName::Genoa: { auto tcb = - quote.reported_tcb().to_policy(product).to_milan_genoa(); + TcbVersionRaw::from_span( + get_report_bytes( + quote.get(), tav_snp_attestation_report_reported_tcb)) + .to_policy(product) + .to_milan_genoa(); boot_loader = fmt::format("{}", tcb.boot_loader); tee = fmt::format("{}", tcb.tee); snp = fmt::format("{}", tcb.snp); @@ -644,7 +627,12 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== } case ProductName::Turin: { - auto tcb = quote.reported_tcb().to_policy(product).to_turin(); + auto tcb = + TcbVersionRaw::from_span( + get_report_bytes( + quote.get(), tav_snp_attestation_report_reported_tcb)) + .to_policy(product) + .to_turin(); boot_loader = fmt::format("{}", tcb.boot_loader); tee = fmt::format("{}", tcb.tee); snp = fmt::format("{}", tcb.snp); @@ -707,7 +695,7 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== { const auto* report = reinterpret_cast("e); return make_endorsement_endpoint_configuration( - AttestationReport::from_unverified({report, attestation_report_size}), + parse_attestation_report_unverified({report, attestation_report_size}), endorsements_servers); } @@ -716,7 +704,7 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== public: [[deprecated( "Use get_attestation_bytes() from ccf/pal/snp_ioctl.h and " - "AttestationReport::from_unverified")]] [[nodiscard]] virtual const snp:: + "parse_attestation_report_unverified")]] [[nodiscard]] virtual const snp:: Attestation& get() const = 0; [[deprecated("Use get_attestation_bytes() from ccf/pal/snp_ioctl.h")]] diff --git a/include/ccf/pal/snp_ioctl6.h b/include/ccf/pal/snp_ioctl6.h index 7dbead593bb4..6261187f58dd 100644 --- a/include/ccf/pal/snp_ioctl6.h +++ b/include/ccf/pal/snp_ioctl6.h @@ -342,7 +342,7 @@ namespace ccf::pal::snp::ioctl6 #pragma GCC diagnostic ignored "-Wdeprecated-declarations" [[deprecated( "Use get_attestation_bytes() and " - "AttestationReport::from_unverified")]] [[nodiscard]] const ccf::pal:: + "parse_attestation_report_unverified")]] [[nodiscard]] const ccf::pal:: snp::Attestation& get() const override { diff --git a/src/js/extensions/snp_attestation.cpp b/src/js/extensions/snp_attestation.cpp index 5583cbb9a98f..97bfc02f6e32 100644 --- a/src/js/extensions/snp_attestation.cpp +++ b/src/js/extensions/snp_attestation.cpp @@ -22,9 +22,10 @@ namespace ccf::js::extensions { JSValue make_js_tcb_version( - js::core::Context& jsctx, pal::snp::TcbVersionRaw tcb) + js::core::Context& jsctx, std::span tcb) { - auto data_hex = jsctx.new_string(tcb.to_hex()); + auto data_hex = + jsctx.new_string(pal::snp::TcbVersionRaw::from_span(tcb).to_hex()); JS_CHECK_EXC(data_hex); return data_hex.take(); } @@ -103,7 +104,7 @@ namespace ccf::js::extensions try { - verified_attestation.emplace(pal::snp::AttestationReport::verify( + verified_attestation.emplace(pal::snp::verify_attestation_report( quote_info.quote, quote_info.endorsements, measurement, @@ -131,44 +132,59 @@ namespace ccf::js::extensions auto a = jsctx.new_obj(); JS_CHECK_EXC(a); - JS_CHECK_SET(a.set_uint32("version", attestation.version())); - JS_CHECK_SET(a.set_uint32("guest_svn", attestation.guest_svn())); + JS_CHECK_SET(a.set_uint32( + "version", tav_snp_attestation_report_version(attestation.get()))); + JS_CHECK_SET(a.set_uint32( + "guest_svn", tav_snp_attestation_report_guest_svn(attestation.get()))); auto policy = jsctx.new_obj(); JS_CHECK_EXC(policy); - JS_CHECK_SET( - policy.set_uint32("abi_minor", attestation.policy_abi_minor())); - JS_CHECK_SET( - policy.set_uint32("abi_major", attestation.policy_abi_major())); - JS_CHECK_SET(policy.set_uint32("smt", attestation.policy_smt())); - JS_CHECK_SET( - policy.set_uint32("migrate_ma", attestation.policy_migrate_ma())); - JS_CHECK_SET(policy.set_uint32("debug", attestation.policy_debug())); - JS_CHECK_SET( - policy.set_uint32("single_socket", attestation.policy_single_socket())); + JS_CHECK_SET(policy.set_uint32( + "abi_minor", + tav_snp_attestation_report_policy_abi_minor(attestation.get()))); + JS_CHECK_SET(policy.set_uint32( + "abi_major", + tav_snp_attestation_report_policy_abi_major(attestation.get()))); + JS_CHECK_SET(policy.set_uint32( + "smt", tav_snp_attestation_report_policy_smt(attestation.get()))); + JS_CHECK_SET(policy.set_uint32( + "migrate_ma", + tav_snp_attestation_report_policy_migrate_ma(attestation.get()))); + JS_CHECK_SET(policy.set_uint32( + "debug", tav_snp_attestation_report_policy_debug(attestation.get()))); + JS_CHECK_SET(policy.set_uint32( + "single_socket", + tav_snp_attestation_report_policy_single_socket(attestation.get()))); JS_CHECK_SET(a.set("policy", std::move(policy))); { - auto family_id = jsctx.new_array_buffer_copy(attestation.family_id()); + auto family_id = jsctx.new_array_buffer_copy(pal::snp::get_report_bytes( + attestation.get(), tav_snp_attestation_report_family_id)); JS_CHECK_EXC(family_id); JS_CHECK_SET(a.set("family_id", std::move(family_id))); } { - auto image_id = jsctx.new_array_buffer_copy(attestation.image_id()); + auto image_id = jsctx.new_array_buffer_copy(pal::snp::get_report_bytes( + attestation.get(), tav_snp_attestation_report_image_id)); JS_CHECK_EXC(image_id); JS_CHECK_SET(a.set("image_id", std::move(image_id))); } - JS_CHECK_SET(a.set_uint32("vmpl", attestation.vmpl())); JS_CHECK_SET(a.set_uint32( - "signature_algo", static_cast(attestation.signature_algo()))); + "vmpl", tav_snp_attestation_report_vmpl(attestation.get()))); + JS_CHECK_SET(a.set_uint32( + "signature_algo", + static_cast( + tav_snp_attestation_report_signature_algo(attestation.get())))); { - auto platform_version = jsctx.wrap( - make_js_tcb_version(jsctx, attestation.platform_version())); + auto platform_version = jsctx.wrap(make_js_tcb_version( + jsctx, + pal::snp::get_report_bytes( + attestation.get(), tav_snp_attestation_report_platform_version))); JS_CHECK_EXC(platform_version); JS_CHECK_SET(a.set("platform_version", std::move(platform_version))); } @@ -176,7 +192,8 @@ namespace ccf::js::extensions { auto platform_info = jsctx.new_obj(); JS_CHECK_EXC(platform_info); - const auto raw_platform_info = attestation.platform_info(); + const auto raw_platform_info = + tav_snp_attestation_report_platform_info(attestation.get()); JS_CHECK_SET(platform_info.set_uint32("smt_en", raw_platform_info & 1)); JS_CHECK_SET( platform_info.set_uint32("tsme_en", (raw_platform_info >> 1) & 1)); @@ -186,39 +203,46 @@ namespace ccf::js::extensions { auto flags = jsctx.new_obj(); JS_CHECK_EXC(flags); - JS_CHECK_SET( - flags.set_uint32("author_key_en", attestation.flags_author_key_en())); - JS_CHECK_SET( - flags.set_uint32("mask_chip_key", attestation.flags_mask_chip_key())); - JS_CHECK_SET( - flags.set_uint32("signing_key", attestation.flags_signing_key())); + JS_CHECK_SET(flags.set_uint32( + "author_key_en", + tav_snp_attestation_report_flags_author_key_en(attestation.get()))); + JS_CHECK_SET(flags.set_uint32( + "mask_chip_key", + tav_snp_attestation_report_flags_mask_chip_key(attestation.get()))); + JS_CHECK_SET(flags.set_uint32( + "signing_key", + tav_snp_attestation_report_flags_signing_key(attestation.get()))); JS_CHECK_SET(a.set("flags", std::move(flags))); } { auto attestation_report_data = - jsctx.new_array_buffer_copy(attestation.report_data()); + jsctx.new_array_buffer_copy(pal::snp::get_report_bytes( + attestation.get(), tav_snp_attestation_report_report_data)); JS_CHECK_EXC(attestation_report_data); JS_CHECK_SET(a.set("report_data", std::move(attestation_report_data))); } { auto attestation_measurement = - jsctx.new_array_buffer_copy(attestation.measurement()); + jsctx.new_array_buffer_copy(pal::snp::get_report_bytes( + attestation.get(), tav_snp_attestation_report_measurement)); JS_CHECK_EXC(attestation_measurement); JS_CHECK_SET(a.set("measurement", std::move(attestation_measurement))); } { auto attestation_host_data = - jsctx.new_array_buffer_copy(attestation.host_data()); + jsctx.new_array_buffer_copy(pal::snp::get_report_bytes( + attestation.get(), tav_snp_attestation_report_host_data)); JS_CHECK_EXC(attestation_host_data); JS_CHECK_SET(a.set("host_data", std::move(attestation_host_data))); } { auto attestation_id_key_digest = - jsctx.new_array_buffer_copy(attestation.id_key_digest()); + jsctx.new_array_buffer_copy(pal::snp::get_report_bytes( + attestation.get(), tav_snp_attestation_report_id_key_digest)); JS_CHECK_EXC(attestation_id_key_digest); JS_CHECK_SET( a.set("id_key_digest", std::move(attestation_id_key_digest))); @@ -226,7 +250,8 @@ namespace ccf::js::extensions { auto attestation_author_key_digest = - jsctx.new_array_buffer_copy(attestation.author_key_digest()); + jsctx.new_array_buffer_copy(pal::snp::get_report_bytes( + attestation.get(), tav_snp_attestation_report_author_key_digest)); JS_CHECK_EXC(attestation_author_key_digest); JS_CHECK_SET( a.set("author_key_digest", std::move(attestation_author_key_digest))); @@ -234,57 +259,81 @@ namespace ccf::js::extensions { auto attestation_report_id = - jsctx.new_array_buffer_copy(attestation.report_id()); + jsctx.new_array_buffer_copy(pal::snp::get_report_bytes( + attestation.get(), tav_snp_attestation_report_report_id)); JS_CHECK_EXC(attestation_report_id); JS_CHECK_SET(a.set("report_id", std::move(attestation_report_id))); } { auto attestation_report_id_ma = - jsctx.new_array_buffer_copy(attestation.report_id_ma()); + jsctx.new_array_buffer_copy(pal::snp::get_report_bytes( + attestation.get(), tav_snp_attestation_report_report_id_ma)); JS_CHECK_EXC(attestation_report_id_ma); JS_CHECK_SET( a.set("report_id_ma", std::move(attestation_report_id_ma))); } { - auto reported_tcb = - jsctx.wrap(make_js_tcb_version(jsctx, attestation.reported_tcb())); + auto reported_tcb = jsctx.wrap(make_js_tcb_version( + jsctx, + pal::snp::get_report_bytes( + attestation.get(), tav_snp_attestation_report_reported_tcb))); 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())); + JS_CHECK_SET(a.set_uint32( + "cpuid_fam_id", + tav_snp_attestation_report_cpuid_fam_id(attestation.get()))); + JS_CHECK_SET(a.set_uint32( + "cpuid_mod_id", + tav_snp_attestation_report_cpuid_mod_id(attestation.get()))); + JS_CHECK_SET(a.set_uint32( + "cpuid_step", + tav_snp_attestation_report_cpuid_step(attestation.get()))); { auto attestation_chip_id = - jsctx.new_array_buffer_copy(attestation.chip_id()); + jsctx.new_array_buffer_copy(pal::snp::get_report_bytes( + attestation.get(), tav_snp_attestation_report_chip_id)); JS_CHECK_EXC(attestation_chip_id); JS_CHECK_SET(a.set("chip_id", std::move(attestation_chip_id))); } { - auto committed_tcb = - jsctx.wrap(make_js_tcb_version(jsctx, attestation.committed_tcb())); + auto committed_tcb = jsctx.wrap(make_js_tcb_version( + jsctx, + pal::snp::get_report_bytes( + attestation.get(), tav_snp_attestation_report_committed_tcb))); JS_CHECK_EXC(committed_tcb); JS_CHECK_SET(a.set("committed_tcb", std::move(committed_tcb))); } - JS_CHECK_SET(a.set_uint32("current_minor", attestation.current_minor())); - JS_CHECK_SET(a.set_uint32("current_build", attestation.current_build())); - JS_CHECK_SET(a.set_uint32("current_major", attestation.current_major())); - JS_CHECK_SET( - a.set_uint32("committed_build", attestation.committed_build())); - JS_CHECK_SET( - a.set_uint32("committed_minor", attestation.committed_minor())); - JS_CHECK_SET( - a.set_uint32("committed_major", attestation.committed_major())); + JS_CHECK_SET(a.set_uint32( + "current_minor", + tav_snp_attestation_report_current_minor(attestation.get()))); + JS_CHECK_SET(a.set_uint32( + "current_build", + tav_snp_attestation_report_current_build(attestation.get()))); + JS_CHECK_SET(a.set_uint32( + "current_major", + tav_snp_attestation_report_current_major(attestation.get()))); + JS_CHECK_SET(a.set_uint32( + "committed_build", + tav_snp_attestation_report_committed_build(attestation.get()))); + JS_CHECK_SET(a.set_uint32( + "committed_minor", + tav_snp_attestation_report_committed_minor(attestation.get()))); + JS_CHECK_SET(a.set_uint32( + "committed_major", + tav_snp_attestation_report_committed_major(attestation.get()))); { - auto launch_tcb = - jsctx.wrap(make_js_tcb_version(jsctx, attestation.launch_tcb())); + auto launch_tcb = jsctx.wrap(make_js_tcb_version( + jsctx, + pal::snp::get_report_bytes( + attestation.get(), tav_snp_attestation_report_launch_tcb))); JS_CHECK_EXC(launch_tcb); JS_CHECK_SET(a.set("launch_tcb", std::move(launch_tcb))); } @@ -294,14 +343,16 @@ namespace ccf::js::extensions { auto signature_r = - jsctx.new_array_buffer_copy(attestation.signature_r()); + jsctx.new_array_buffer_copy(pal::snp::get_report_bytes( + attestation.get(), tav_snp_attestation_report_signature_r)); JS_CHECK_EXC(signature_r); JS_CHECK_SET(signature.set("r", std::move(signature_r))); } { auto signature_s = - jsctx.new_array_buffer_copy(attestation.signature_s()); + jsctx.new_array_buffer_copy(pal::snp::get_report_bytes( + attestation.get(), tav_snp_attestation_report_signature_s)); JS_CHECK_EXC(signature_s); JS_CHECK_SET(signature.set("s", std::move(signature_s))); } diff --git a/src/node/node_state.h b/src/node/node_state.h index 6a405b6d406f..9b5f292b2cd9 100644 --- a/src/node/node_state.h +++ b/src/node/node_state.h @@ -884,7 +884,10 @@ namespace ccf AttestationProvider::get_snp_attestation_report(quote_info); if (snp_attestation.has_value()) { - snp_tcb_version = snp_attestation.value().reported_tcb(); + snp_tcb_version = ccf::pal::snp::TcbVersionRaw::from_span( + ccf::pal::snp::get_report_bytes( + snp_attestation.value().get(), + tav_snp_attestation_report_reported_tcb)); } // Verify that the security policy matches the quoted digest of the policy @@ -1033,9 +1036,11 @@ namespace ccf // Check that tcbm in endorsement matches reported TCB in our // retrieved attestation const auto report = - ccf::pal::snp::AttestationReport::from_unverified( + ccf::pal::snp::parse_attestation_report_unverified( quote_info.quote); - const auto reported_tcb = report.reported_tcb(); + const auto reported_tcb = ccf::pal::snp::TcbVersionRaw::from_span( + ccf::pal::snp::get_report_bytes( + report.get(), tav_snp_attestation_report_reported_tcb)); // tcbm is a single hex value, like DB18000000000004. auto tcb_as_hex = reported_tcb.to_hex(); diff --git a/src/node/quote.cpp b/src/node/quote.cpp index 29213ccc4dac..9a50fc99eaa4 100644 --- a/src/node/quote.cpp +++ b/src/node/quote.cpp @@ -165,7 +165,7 @@ namespace ccf { pal::PlatformAttestationMeasurement d = {}; pal::PlatformAttestationReportData r = {}; - return pal::snp::AttestationReport::verify( + return pal::snp::verify_attestation_report( quote_info.quote, quote_info.endorsements, d, @@ -235,13 +235,14 @@ namespace ccf pal::PlatformAttestationReportData r = {}; try { - const auto report = pal::snp::AttestationReport::verify( + const auto report = pal::snp::verify_attestation_report( quote_info.quote, quote_info.endorsements, d, r, quote_info.endorsed_tcb); - const auto host_data = report.host_data(); + const auto host_data = pal::snp::get_report_bytes( + report.get(), tav_snp_attestation_report_host_data); std::copy(host_data.begin(), host_data.end(), rep.begin()); } catch (const std::exception& e) @@ -310,7 +311,7 @@ namespace ccf pal::PlatformAttestationMeasurement d = {}; pal::PlatformAttestationReportData r = {}; - auto attestation = pal::snp::AttestationReport::verify( + auto attestation = pal::snp::verify_attestation_report( quote_info.quote, quote_info.endorsements, d, r, quote_info.endorsed_tcb); std::optional min_tcb_opt = std::nullopt; @@ -320,9 +321,12 @@ namespace ccf const std::string& cpuid_hex, const pal::snp::TcbVersionPolicy& 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()) + cpuid.get_family_id() == + tav_snp_attestation_report_cpuid_fam_id(attestation.get()) && + cpuid.get_model_id() == + tav_snp_attestation_report_cpuid_mod_id(attestation.get()) && + cpuid.stepping == + tav_snp_attestation_report_cpuid_step(attestation.get())) { min_tcb_opt = v; return false; @@ -337,9 +341,13 @@ namespace ccf // CPUID of the attested cpu must now be equal to the min_tcb_opt's cpuid auto product_family = pal::snp::get_sev_snp_product( - attestation.cpuid_fam_id(), attestation.cpuid_mod_id()); + tav_snp_attestation_report_cpuid_fam_id(attestation.get()), + tav_snp_attestation_report_cpuid_mod_id(attestation.get())); auto attestation_tcb_policy = - attestation.reported_tcb().to_policy(product_family); + pal::snp::TcbVersionRaw::from_span( + pal::snp::get_report_bytes( + attestation.get(), tav_snp_attestation_report_reported_tcb)) + .to_policy(product_family); if (pal::snp::TcbVersionPolicy::is_valid( min_tcb_opt.value(), attestation_tcb_policy)) diff --git a/src/pal/attestation.cpp b/src/pal/attestation.cpp index 1d818011c533..dce1679fc571 100644 --- a/src/pal/attestation.cpp +++ b/src/pal/attestation.cpp @@ -8,235 +8,47 @@ #include "ccf/pal/attestation_sev_snp.h" #include "ccf/pal/sev_snp_cpuid.h" #include "ds/internal_logger.h" -#include "pal/tav_ffi.h" #include #include namespace ccf::pal { - namespace snp + namespace { - class AttestationReport::Impl - { - public: - TavAttestationReportPtr report; - - explicit Impl(TavAttestationReportPtr&& report_) : - report(std::move(report_)) - {} - }; - - AttestationReport::AttestationReport(std::unique_ptr impl_) : - impl(std::move(impl_)) - {} - AttestationReport::AttestationReport(AttestationReport&&) noexcept = - default; - AttestationReport& AttestationReport::operator=( - AttestationReport&&) noexcept = default; - AttestationReport::~AttestationReport() = default; - - namespace - { - using BytesAccessor = - void (*)(const TavSnpAttestationReport*, const uint8_t**, size_t*); - - std::span get_bytes( - const TavSnpAttestationReport* report, - BytesAccessor accessor, - size_t expected_size, - std::string_view field) - { - const uint8_t* data = nullptr; - size_t size = 0; - accessor(report, &data, &size); - if (size != expected_size || data == nullptr) - { - throw std::logic_error(fmt::format( - "SEV-SNP: TAV returned {} bytes for {} (data {}), expected {}", - size, - field, - data == nullptr ? "is null" : "is not null", - expected_size)); - } - return {data, size}; - } + using TavErrorPtr = std::unique_ptr; - [[noreturn]] void throw_tav_error( - std::string_view operation, const TavError* error) + void check_tav_error(std::string_view operation, const TavError* error) + { + if (error != nullptr) { - const auto error_code = tav_error_code(error); - const auto* error_message = tav_error_message(error); throw std::logic_error(fmt::format( "SEV-SNP: TAV {} failed ({}): {}", operation, - static_cast(error_code), - error_message == nullptr ? "Unknown TAV error" : error_message)); + static_cast(tav_error_code(error)), + tav_error_message(error))); } } - -#define SNP_SCALAR_ACCESSOR(method, tav_accessor, type) \ - type AttestationReport::method() const \ - { \ - return tav_accessor(impl->report.get()); \ - } - - SNP_SCALAR_ACCESSOR(version, tav_snp_attestation_report_version, uint32_t) - SNP_SCALAR_ACCESSOR( - guest_svn, tav_snp_attestation_report_guest_svn, uint32_t) - SNP_SCALAR_ACCESSOR(policy, tav_snp_attestation_report_policy, uint64_t) - SNP_SCALAR_ACCESSOR( - policy_abi_minor, tav_snp_attestation_report_policy_abi_minor, uint8_t) - SNP_SCALAR_ACCESSOR( - policy_abi_major, tav_snp_attestation_report_policy_abi_major, uint8_t) - SNP_SCALAR_ACCESSOR(policy_smt, tav_snp_attestation_report_policy_smt, bool) - SNP_SCALAR_ACCESSOR( - policy_migrate_ma, tav_snp_attestation_report_policy_migrate_ma, bool) - SNP_SCALAR_ACCESSOR( - policy_debug, tav_snp_attestation_report_policy_debug, bool) - SNP_SCALAR_ACCESSOR( - policy_single_socket, - tav_snp_attestation_report_policy_single_socket, - bool) - SNP_SCALAR_ACCESSOR(vmpl, tav_snp_attestation_report_vmpl, uint32_t) - SNP_SCALAR_ACCESSOR( - signature_algo, tav_snp_attestation_report_signature_algo, uint32_t) - SNP_SCALAR_ACCESSOR( - platform_info, tav_snp_attestation_report_platform_info, uint64_t) - SNP_SCALAR_ACCESSOR(flags, tav_snp_attestation_report_flags, uint32_t) - SNP_SCALAR_ACCESSOR( - flags_author_key_en, tav_snp_attestation_report_flags_author_key_en, bool) - SNP_SCALAR_ACCESSOR( - flags_mask_chip_key, tav_snp_attestation_report_flags_mask_chip_key, bool) - SNP_SCALAR_ACCESSOR( - flags_signing_key, tav_snp_attestation_report_flags_signing_key, uint8_t) - SNP_SCALAR_ACCESSOR( - cpuid_fam_id, tav_snp_attestation_report_cpuid_fam_id, uint8_t) - SNP_SCALAR_ACCESSOR( - cpuid_mod_id, tav_snp_attestation_report_cpuid_mod_id, uint8_t) - SNP_SCALAR_ACCESSOR( - cpuid_step, tav_snp_attestation_report_cpuid_step, uint8_t) - SNP_SCALAR_ACCESSOR( - current_build, tav_snp_attestation_report_current_build, uint8_t) - SNP_SCALAR_ACCESSOR( - current_minor, tav_snp_attestation_report_current_minor, uint8_t) - SNP_SCALAR_ACCESSOR( - current_major, tav_snp_attestation_report_current_major, uint8_t) - SNP_SCALAR_ACCESSOR( - committed_build, tav_snp_attestation_report_committed_build, uint8_t) - SNP_SCALAR_ACCESSOR( - committed_minor, tav_snp_attestation_report_committed_minor, uint8_t) - SNP_SCALAR_ACCESSOR( - committed_major, tav_snp_attestation_report_committed_major, uint8_t) - -#undef SNP_SCALAR_ACCESSOR - -#define SNP_BYTES_ACCESSOR(method, tav_accessor, size) \ - std::span AttestationReport::method() const \ - { \ - return get_bytes(impl->report.get(), tav_accessor, size, #method); \ } - SNP_BYTES_ACCESSOR(family_id, tav_snp_attestation_report_family_id, 16) - SNP_BYTES_ACCESSOR(image_id, tav_snp_attestation_report_image_id, 16) - SNP_BYTES_ACCESSOR( - report_data, - tav_snp_attestation_report_report_data, - snp_attestation_report_data_size) - SNP_BYTES_ACCESSOR( - measurement, - tav_snp_attestation_report_measurement, - snp_attestation_measurement_size) - SNP_BYTES_ACCESSOR(host_data, tav_snp_attestation_report_host_data, 32) - SNP_BYTES_ACCESSOR( - id_key_digest, tav_snp_attestation_report_id_key_digest, 48) - SNP_BYTES_ACCESSOR( - author_key_digest, tav_snp_attestation_report_author_key_digest, 48) - SNP_BYTES_ACCESSOR(report_id, tav_snp_attestation_report_report_id, 32) - SNP_BYTES_ACCESSOR( - report_id_ma, tav_snp_attestation_report_report_id_ma, 32) - SNP_BYTES_ACCESSOR(chip_id, tav_snp_attestation_report_chip_id, 64) - SNP_BYTES_ACCESSOR(signature_r, tav_snp_attestation_report_signature_r, 72) - SNP_BYTES_ACCESSOR(signature_s, tav_snp_attestation_report_signature_s, 72) - -#undef SNP_BYTES_ACCESSOR - - TcbVersionRaw AttestationReport::platform_version() const - { - return TcbVersionRaw::from_span(get_bytes( - impl->report.get(), - tav_snp_attestation_report_platform_version, - snp_tcb_version_size, - "platform_version")); - } - - TcbVersionRaw AttestationReport::reported_tcb() const - { - return TcbVersionRaw::from_span(get_bytes( - impl->report.get(), - tav_snp_attestation_report_reported_tcb, - snp_tcb_version_size, - "reported_tcb")); - } - - TcbVersionRaw AttestationReport::committed_tcb() const - { - return TcbVersionRaw::from_span(get_bytes( - impl->report.get(), - tav_snp_attestation_report_committed_tcb, - snp_tcb_version_size, - "committed_tcb")); - } - - TcbVersionRaw AttestationReport::launch_tcb() const - { - return TcbVersionRaw::from_span(get_bytes( - impl->report.get(), - tav_snp_attestation_report_launch_tcb, - snp_tcb_version_size, - "launch_tcb")); - } - - std::span AttestationReport::chip_id_for_vcek() const - { - auto id = chip_id(); - const auto product = get_sev_snp_product(cpuid_fam_id(), cpuid_mod_id()); - if (product == ProductName::Milan || product == ProductName::Genoa) - { - return id; - } - if (product == ProductName::Turin) - { - return id.first(8); - } - throw std::logic_error( - fmt::format("Unsupported SEV-SNP product: {}", product)); - } - - AttestationReport AttestationReport::from_unverified( + namespace snp + { + AttestationReport parse_attestation_report_unverified( std::span report) { TavSnpAttestationReport* raw_report = nullptr; - TavErrorPtr error(tav_snp_attestation_report_from_unverified_bytes( - report.data(), report.size(), &raw_report)); - TavAttestationReportPtr parsed_report(raw_report); - if (error != nullptr) - { - throw_tav_error("unverified report parsing", error.get()); - } + TavErrorPtr error( + tav_snp_attestation_report_from_unverified_bytes( + report.data(), report.size(), &raw_report), + tav_error_free); + AttestationReport parsed_report(raw_report); + check_tav_error("unverified report parsing", error.get()); if (parsed_report == nullptr) { throw std::logic_error( "SEV-SNP: TAV parsing succeeded without returning a report"); } - return AttestationReport( - std::make_unique(std::move(parsed_report))); - } - - AttestationReport parse_attestation_report_unverified( - std::span report) - { - return AttestationReport::from_unverified(report); + return parsed_report; } } @@ -433,7 +245,7 @@ namespace ccf::pal } // Verifying SNP attestation report is available on all platforms. - snp::AttestationReport snp::AttestationReport::verify( + snp::AttestationReport snp::verify_attestation_report( std::span report, std::span endorsements, PlatformAttestationMeasurement& measurement, @@ -464,61 +276,54 @@ namespace ccf::pal auto ask_cert = certificates[1]; auto ark_cert = certificates[2]; - TavSnpAttestationReport* verified_report_raw = nullptr; - TavErrorPtr verification_error(tav_verify_snp_attestation( - report.data(), - report.size(), - ark_cert.data(), - ark_cert.size(), - ask_cert.data(), - ask_cert.size(), - vcek_cert.data(), - vcek_cert.size(), - &verified_report_raw)); - TavAttestationReportPtr verified_report(verified_report_raw); - if (verification_error != nullptr) - { - const auto error_code = tav_error_code(verification_error.get()); - const auto* error_message = tav_error_message(verification_error.get()); - throw std::logic_error(fmt::format( - "SEV-SNP: TAV verification failed ({}): {}", - static_cast(error_code), - error_message == nullptr ? "Unknown TAV error" : error_message)); - } - - if (verified_report == nullptr) + TavSnpAttestationReport* raw_report = nullptr; + TavErrorPtr error( + tav_verify_snp_attestation( + report.data(), + report.size(), + ark_cert.data(), + ark_cert.size(), + ask_cert.data(), + ask_cert.size(), + vcek_cert.data(), + vcek_cert.size(), + &raw_report), + tav_error_free); + AttestationReport attestation(raw_report); + check_tav_error("verification", error.get()); + if (attestation == nullptr) { throw std::logic_error( "SEV-SNP: TAV verification succeeded without returning a report"); } - auto attestation = - AttestationReport(std::make_unique(std::move(verified_report))); - - if (attestation.version() < snp::minimum_attestation_version) + if ( + tav_snp_attestation_report_version(attestation.get()) < + snp::minimum_attestation_version) { throw std::logic_error(fmt::format( "SEV-SNP: Attestation version is {} not >= expected minimum {}", - attestation.version(), + tav_snp_attestation_report_version(attestation.get()), snp::minimum_attestation_version)); } const auto product_family = snp::get_sev_snp_product( - attestation.cpuid_fam_id(), attestation.cpuid_mod_id()); + tav_snp_attestation_report_cpuid_fam_id(attestation.get()), + tav_snp_attestation_report_cpuid_mod_id(attestation.get())); // ---- Verify attestation report contents ---- if ( - attestation.flags_signing_key() != + tav_snp_attestation_report_flags_signing_key(attestation.get()) != snp::attestation_flags_signing_key_vcek) { throw std::logic_error(fmt::format( "SEV-SNP: Attestation report must be signed by VCEK: {}", - attestation.flags_signing_key())); + tav_snp_attestation_report_flags_signing_key(attestation.get()))); } // mask_chip_key if set means the operator set the vcek to 0s - if (attestation.flags_mask_chip_key()) + if (tav_snp_attestation_report_flags_mask_chip_key(attestation.get())) { throw std::logic_error( fmt::format("SEV-SNP: Mask chip key must not be set")); @@ -527,15 +332,15 @@ namespace ccf::pal // All attestation reports generated by guests must have VMPL <= 3 // while host generated reports have VMPL > 3. // We should reject host generated reports. - if (attestation.vmpl() > 3) + if (tav_snp_attestation_report_vmpl(attestation.get()) > 3) { throw std::logic_error(fmt::format( "SEV-SNP: This report seems to be host generated (VMPL {} > 3)", - attestation.vmpl())); + tav_snp_attestation_report_vmpl(attestation.get()))); } // Debug mode would allow decryption of guest pages - if (attestation.policy_debug()) + if (tav_snp_attestation_report_policy_debug(attestation.get())) { throw std::logic_error( "SEV-SNP: SNP attestation report guest policy debugging must not be " @@ -544,7 +349,7 @@ namespace ccf::pal // Migration of CCF nodes and other services could allow duplicates, and // hence must be disallowed - if (attestation.policy_migrate_ma()) + if (tav_snp_attestation_report_policy_migrate_ma(attestation.get())) { throw std::logic_error( "SEV-SNP: SNP attestation report guest policy migration must not be " @@ -555,7 +360,11 @@ namespace ccf::pal if (endorsed_tcb.has_value()) { auto endorsed_tcb_policy = endorsed_tcb->to_policy(product_family); - auto reported_tcb = attestation.reported_tcb().to_policy(product_family); + auto reported_tcb = + TcbVersionRaw::from_span( + snp::get_report_bytes( + attestation.get(), tav_snp_attestation_report_reported_tcb)) + .to_policy(product_family); if (!snp::TcbVersionPolicy::is_valid(endorsed_tcb_policy, reported_tcb)) { @@ -568,7 +377,7 @@ namespace ccf::pal } auto endorsed_chip_id = get_endorsed_chip_id_from_cert(vcek_cert); - auto reported_chip_id = attestation.chip_id_for_vcek(); + auto reported_chip_id = get_chip_id_for_vcek(attestation); if ( endorsed_chip_id.has_value() && (endorsed_chip_id->size() != reported_chip_id.size() || @@ -589,7 +398,8 @@ namespace ccf::pal auto raw_endorsed_tcb = snp::TcbVersionRaw::from_hex(std::string(claimed_endorsed_tcb.value())); - const auto reported_tcb = attestation.reported_tcb(); + const auto reported_tcb = TcbVersionRaw::from_span(snp::get_report_bytes( + attestation.get(), tav_snp_attestation_report_reported_tcb)); if (raw_endorsed_tcb != reported_tcb) { auto endorsed_tcb_hex = raw_endorsed_tcb.to_hex(); @@ -603,8 +413,10 @@ namespace ccf::pal // ---- Set return values ---- - report_data = SnpAttestationReportData(attestation.report_data()); - measurement = SnpAttestationMeasurement(attestation.measurement()); + report_data = SnpAttestationReportData(snp::get_report_bytes( + attestation.get(), tav_snp_attestation_report_report_data)); + measurement = SnpAttestationMeasurement(snp::get_report_bytes( + attestation.get(), tav_snp_attestation_report_measurement)); return attestation; } @@ -620,7 +432,7 @@ namespace ccf::pal quote_info.format)); } - return snp::AttestationReport::verify( + return snp::verify_attestation_report( quote_info.quote, quote_info.endorsements, measurement, @@ -640,7 +452,7 @@ namespace ccf::pal quote_info.format)); } - snp::AttestationReport::verify( + snp::verify_attestation_report( quote_info.quote, quote_info.endorsements, measurement, diff --git a/src/pal/quote_generation.h b/src/pal/quote_generation.h index d66d8b33805a..b11e5519d869 100644 --- a/src/pal/quote_generation.h +++ b/src/pal/quote_generation.h @@ -91,14 +91,16 @@ namespace ccf::pal node_quote_info.format = QuoteFormat::amd_sev_snp_v1; node_quote_info.quote = snp::get_attestation_bytes(report_data); auto report = - snp::AttestationReport::from_unverified(node_quote_info.quote); + snp::parse_attestation_report_unverified(node_quote_info.quote); - if (report.version() < pal::snp::minimum_attestation_version) + if ( + tav_snp_attestation_report_version(report.get()) < + pal::snp::minimum_attestation_version) { throw std::logic_error(fmt::format( "SEV-SNP: attestation version {} is less than the minimum supported " "version {}", - report.version(), + tav_snp_attestation_report_version(report.get()), pal::snp::minimum_attestation_version)); } diff --git a/src/pal/tav_ffi.h b/src/pal/tav_ffi.h deleted file mode 100644 index 1ff00bf70c5f..000000000000 --- a/src/pal/tav_ffi.h +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the Apache 2.0 License. - -#pragma once - -#include "tav/snp.h" -#include "tav/utils.h" - -#include - -namespace ccf::pal -{ - template - struct TavDeleter - { - void operator()(T* ptr) const noexcept - { - Free(ptr); - } - }; - - template - using TavUniquePtr = std::unique_ptr>; - - using TavErrorPtr = TavUniquePtr; - using TavAttestationReportPtr = - TavUniquePtr; -} diff --git a/src/pal/test/snp_attestation_validation.cpp b/src/pal/test/snp_attestation_validation.cpp index 3a36ba0b7035..724c34b3f941 100644 --- a/src/pal/test/snp_attestation_validation.cpp +++ b/src/pal/test/snp_attestation_validation.cpp @@ -135,93 +135,163 @@ namespace TEST_CASE("unverified SNP report accessors") { using namespace ccf::pal; - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + static_assert( + std:: + is_same_v); + static_assert(!std::is_copy_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); static_assert(std::is_same_v< decltype(snp::parse_attestation_report_unverified( std::declval>())), snp::AttestationReport>); -#pragma GCC diagnostic pop auto report = - snp::AttestationReport::from_unverified(snp::testing::milan_attestation); + snp::parse_attestation_report_unverified(snp::testing::milan_attestation); - CHECK(report.version() == 3); - CHECK(report.cpuid_fam_id() == 25); - CHECK(report.cpuid_mod_id() == 1); - CHECK(report.reported_tcb().to_hex() == "db18000000000004"); - const auto measurement = report.measurement(); + CHECK(tav_snp_attestation_report_version(report.get()) == 3); + CHECK(tav_snp_attestation_report_cpuid_fam_id(report.get()) == 25); + CHECK(tav_snp_attestation_report_cpuid_mod_id(report.get()) == 1); + CHECK( + snp::TcbVersionRaw::from_span( + ccf::pal::snp::get_report_bytes( + report.get(), tav_snp_attestation_report_reported_tcb)) + .to_hex() == "db18000000000004"); + const auto measurement = ccf::pal::snp::get_report_bytes( + report.get(), tav_snp_attestation_report_measurement); auto moved_report = std::move(report); CHECK(measurement.size() == snp_attestation_measurement_size); - CHECK(moved_report.signature_r().size() == 72); + CHECK( + ccf::pal::snp::get_report_bytes( + moved_report.get(), tav_snp_attestation_report_signature_r) + .size() == 72); +} + +TEST_CASE("CCF policy is separate from generic TAV verification") +{ + using namespace ccf::pal; + const auto certs = milan_endorsement_certs(); + REQUIRE(certs.size() == 3); + TavSnpAttestationReport* raw_report = nullptr; + const std::unique_ptr error( + tav_verify_snp_attestation( + snp::testing::milan_attestation.data(), + snp::testing::milan_attestation.size(), + certs[2].data(), + certs[2].size(), + certs[1].data(), + certs[1].size(), + certs[0].data(), + certs[0].size(), + &raw_report), + tav_error_free); + const snp::AttestationReport report(raw_report); + REQUIRE(error == nullptr); + REQUIRE(report != nullptr); + CHECK( + tav_snp_attestation_report_version(report.get()) == + snp::minimum_attestation_version); + + PlatformAttestationMeasurement measurement; + PlatformAttestationReportData report_data; + const std::vector endorsements( + snp::testing::milan_endorsements.begin(), + snp::testing::milan_endorsements.end()); + CHECK_THROWS_WITH_AS( + snp::verify_attestation_report( + snp::testing::milan_attestation, + endorsements, + measurement, + report_data, + "0000000000000000"), + doctest::Contains("does not match reported TCB"), + std::logic_error); } TEST_CASE("unverified SNP report rejects invalid sizes") { CHECK_THROWS_WITH_AS( - ccf::pal::snp::AttestationReport::from_unverified( - std::vector(100)), - doctest::Contains( - "SEV-SNP: TAV unverified report parsing failed (1): Invalid " - "attestation report: expected 1184 bytes, got 100"), + static_cast(ccf::pal::snp::parse_attestation_report_unverified( + std::vector(100))), + "SEV-SNP: TAV unverified report parsing failed (1): Invalid " + "attestation report: expected 1184 bytes, got 100", std::logic_error); } TEST_CASE("SNP byte accessors borrow report storage") { - using Report = ccf::pal::snp::AttestationReport; struct ByteField { - std::span (Report::*accessor)() const; + ccf::pal::snp::ReportBytesAccessor accessor; size_t offset; size_t size; }; const ByteField fields[] = { - {&Report::family_id, 0x010, 16}, - {&Report::image_id, 0x020, 16}, - {&Report::report_data, 0x050, 64}, - {&Report::measurement, 0x090, 48}, - {&Report::host_data, 0x0C0, 32}, - {&Report::id_key_digest, 0x0E0, 48}, - {&Report::author_key_digest, 0x110, 48}, - {&Report::report_id, 0x140, 32}, - {&Report::report_id_ma, 0x160, 32}, - {&Report::chip_id, 0x1A0, 64}, - {&Report::chip_id_for_vcek, 0x1A0, 64}, - {&Report::signature_r, 0x2A0, 72}, - {&Report::signature_s, 0x2E8, 72}}; + {tav_snp_attestation_report_family_id, 0x010, 16}, + {tav_snp_attestation_report_image_id, 0x020, 16}, + {tav_snp_attestation_report_platform_version, 0x038, 8}, + {tav_snp_attestation_report_report_data, 0x050, 64}, + {tav_snp_attestation_report_measurement, 0x090, 48}, + {tav_snp_attestation_report_host_data, 0x0C0, 32}, + {tav_snp_attestation_report_id_key_digest, 0x0E0, 48}, + {tav_snp_attestation_report_author_key_digest, 0x110, 48}, + {tav_snp_attestation_report_report_id, 0x140, 32}, + {tav_snp_attestation_report_report_id_ma, 0x160, 32}, + {tav_snp_attestation_report_reported_tcb, 0x180, 8}, + {tav_snp_attestation_report_chip_id, 0x1A0, 64}, + {tav_snp_attestation_report_committed_tcb, 0x1E0, 8}, + {tav_snp_attestation_report_launch_tcb, 0x1F0, 8}, + {tav_snp_attestation_report_signature_r, 0x2A0, 72}, + {tav_snp_attestation_report_signature_s, 0x2E8, 72}}; const auto& raw_report = ccf::pal::snp::testing::milan_attestation; - auto report = Report::from_unverified(raw_report); + auto report = ccf::pal::snp::parse_attestation_report_unverified(raw_report); for (const auto& [accessor, offset, size] : fields) { - const auto first = (report.*accessor)(); - const auto second = (report.*accessor)(); + const auto first = ccf::pal::snp::get_report_bytes(report.get(), accessor); + const auto second = ccf::pal::snp::get_report_bytes(report.get(), accessor); CHECK(first.data() == second.data()); REQUIRE(first.size() == size); CHECK(std::equal(first.begin(), first.end(), raw_report.begin() + offset)); } } +TEST_CASE("SNP borrowed byte access rejects empty handles") +{ + ccf::pal::snp::AttestationReport report; + CHECK_THROWS_WITH_AS( + ccf::pal::snp::get_report_bytes( + report.get(), tav_snp_attestation_report_measurement), + "Cannot access an empty SNP attestation report", + std::logic_error); +} + TEST_CASE("SNP borrowed bytes survive report ownership transfers") { using namespace ccf::pal::snp; auto raw_report = testing::milan_attestation; std::optional original = - AttestationReport::from_unverified(raw_report); - const auto measurement = original->measurement(); + parse_attestation_report_unverified(raw_report); + const auto measurement = ccf::pal::snp::get_report_bytes( + original->get(), tav_snp_attestation_report_measurement); raw_report[0x090] ^= 0xff; CHECK(measurement[0] == testing::milan_attestation[0x090]); auto moved = std::move(*original); original.reset(); - CHECK(measurement.data() == moved.measurement().data()); + CHECK( + measurement.data() == + ccf::pal::snp::get_report_bytes( + moved.get(), tav_snp_attestation_report_measurement) + .data()); auto assigned = - AttestationReport::from_unverified(testing::genoa_attestation); + parse_attestation_report_unverified(testing::genoa_attestation); assigned = std::move(moved); - CHECK(measurement.data() == assigned.measurement().data()); + CHECK( + measurement.data() == + ccf::pal::snp::get_report_bytes( + assigned.get(), tav_snp_attestation_report_measurement) + .data()); CHECK(std::equal( measurement.begin(), measurement.end(), @@ -236,13 +306,16 @@ TEST_CASE("VCEK chip ID borrows the product-specific prefix") &testing::genoa_attestation, &testing::turin_attestation}) { - auto report = AttestationReport::from_unverified(*raw_report); - const auto chip_id = report.chip_id(); - const auto vcek_chip_id = report.chip_id_for_vcek(); + auto report = parse_attestation_report_unverified(*raw_report); + const auto chip_id = ccf::pal::snp::get_report_bytes( + report.get(), tav_snp_attestation_report_chip_id); + const auto vcek_chip_id = get_chip_id_for_vcek(report); CHECK(vcek_chip_id.data() == chip_id.data()); CHECK( vcek_chip_id.size() == - (get_sev_snp_product(report.cpuid_fam_id(), report.cpuid_mod_id()) == + (get_sev_snp_product( + tav_snp_attestation_report_cpuid_fam_id(report.get()), + tav_snp_attestation_report_cpuid_mod_id(report.get())) == ProductName::Turin ? 8 : 64)); @@ -269,7 +342,7 @@ TEST_CASE("SNP verification preserves invalid size error") ccf::pal::PlatformAttestationMeasurement measurement; ccf::pal::PlatformAttestationReportData report_data; CHECK_THROWS_WITH_AS( - ccf::pal::snp::AttestationReport::verify( + ccf::pal::snp::verify_attestation_report( std::vector(100), {}, measurement, report_data), doctest::Contains( "Input SEV-SNP attestation report is not of expected size 1184: 100"), @@ -562,11 +635,14 @@ TEST_CASE("Parsing of Tcb versions from strings") TEST_CASE("Parsing tcb versions from attestaion") { - auto milan_attestation = ccf::pal::snp::AttestationReport::from_unverified( + auto milan_attestation = ccf::pal::snp::parse_attestation_report_unverified( ccf::pal::snp::testing::milan_attestation); - auto milan_tcb = milan_attestation.reported_tcb() - .to_policy(ccf::pal::snp::ProductName::Milan) - .to_milan_genoa(); + auto milan_tcb = + ccf::pal::snp::TcbVersionRaw::from_span( + ccf::pal::snp::get_report_bytes( + milan_attestation.get(), tav_snp_attestation_report_reported_tcb)) + .to_policy(ccf::pal::snp::ProductName::Milan) + .to_milan_genoa(); CHECK_EQ(milan_tcb.microcode, 0xdb); CHECK_EQ(milan_tcb.snp, 0x18); CHECK_EQ(milan_tcb.tee, 0x00); @@ -780,7 +856,8 @@ TEST_CASE("Quote endorsements url generation") for (auto [attestation, servers, expected_url] : test_cases) { - auto quote = ccf::pal::snp::AttestationReport::from_unverified(attestation); + auto quote = + ccf::pal::snp::parse_attestation_report_unverified(attestation); auto config = ccf::pal::snp::make_endorsement_endpoint_configuration(quote, servers); @@ -791,12 +868,17 @@ TEST_CASE("Quote endorsements url generation") TEST_CASE("Quote endorsements generation for v2 attestation version fails") { auto v2_format_milan_attestation = - ccf::pal::snp::AttestationReport::from_unverified( + ccf::pal::snp::parse_attestation_report_unverified( ccf::pal::snp::testing::v2_format_milan_attestation); - CHECK_EQ(v2_format_milan_attestation.version(), 2); - CHECK_EQ(v2_format_milan_attestation.cpuid_fam_id(), 0x0); - CHECK_EQ(v2_format_milan_attestation.cpuid_mod_id(), 0x0); + CHECK_EQ( + tav_snp_attestation_report_version(v2_format_milan_attestation.get()), 2); + CHECK_EQ( + tav_snp_attestation_report_cpuid_fam_id(v2_format_milan_attestation.get()), + 0x0); + CHECK_EQ( + tav_snp_attestation_report_cpuid_mod_id(v2_format_milan_attestation.get()), + 0x0); CHECK_THROWS_WITH( ccf::pal::snp::make_endorsement_endpoint_configuration( @@ -823,7 +905,7 @@ TEST_CASE("Extracting metadata from endorsements") }; auto attestation = - pal::snp::AttestationReport::from_unverified(milan_quote_info.quote); + pal::snp::parse_attestation_report_unverified(milan_quote_info.quote); auto certificates = ccf::crypto::split_x509_cert_bundle(std::string_view( reinterpret_cast(milan_quote_info.endorsements.data()), @@ -836,11 +918,15 @@ TEST_CASE("Extracting metadata from endorsements") REQUIRE(endorsed_tcb.has_value()); CHECK_EQ( nlohmann::json(endorsed_tcb.value()).dump(), - nlohmann::json(attestation.reported_tcb()).dump()); + nlohmann::json( + pal::snp::TcbVersionRaw::from_span(ccf::pal::snp::get_report_bytes( + attestation.get(), tav_snp_attestation_report_reported_tcb))) + .dump()); auto endorsed_chip_id = pal::get_endorsed_chip_id_from_cert(chip_certificate); REQUIRE(endorsed_chip_id.has_value()); - auto printable_reported_chip_id = attestation.chip_id(); + auto printable_reported_chip_id = ccf::pal::snp::get_report_bytes( + attestation.get(), tav_snp_attestation_report_chip_id); CHECK_EQ( ds::to_hex(endorsed_chip_id.value()), ds::to_hex(printable_reported_chip_id)); diff --git a/src/pal/test/snp_ioctl_test.cpp b/src/pal/test/snp_ioctl_test.cpp index 01d2595198b5..2ec67a41e274 100644 --- a/src/pal/test/snp_ioctl_test.cpp +++ b/src/pal/test/snp_ioctl_test.cpp @@ -26,9 +26,10 @@ TEST_CASE("SNP request attestation") const auto report_bytes = snp::get_attestation_bytes(report_data); REQUIRE(report_bytes.size() == snp::attestation_report_size); const auto attestation = - snp::AttestationReport::from_unverified(report_bytes); + snp::parse_attestation_report_unverified(report_bytes); - SnpAttestationReportData attested_report_data(attestation.report_data()); + SnpAttestationReportData attested_report_data(snp::get_report_bytes( + attestation.get(), tav_snp_attestation_report_report_data)); REQUIRE_EQ(snp_report_data.report_data, attested_report_data.report_data); } diff --git a/src/pal/test/verify_attestation.cpp b/src/pal/test/verify_attestation.cpp index ae9893b08c97..69cb21827136 100644 --- a/src/pal/test/verify_attestation.cpp +++ b/src/pal/test/verify_attestation.cpp @@ -19,7 +19,7 @@ void fetch_endorsements( const std::vector& attestation_raw, std::vector& output) { auto attestation = - ccf::pal::snp::AttestationReport::from_unverified(attestation_raw); + ccf::pal::snp::parse_attestation_report_unverified(attestation_raw); auto endorsement_config = ccf::pal::snp::make_endorsement_endpoint_configuration( @@ -66,8 +66,8 @@ int main(int argc, char** argv) ->check([](const std::string& attestation_hex) { try { - ccf::pal::snp::AttestationReport::from_unverified( - ccf::ds::from_hex(attestation_hex)); + static_cast(ccf::pal::snp::parse_attestation_report_unverified( + ccf::ds::from_hex(attestation_hex))); return std::string(); } catch (const std::exception& e) diff --git a/src/pal/test/verify_uvm_attestation_and_endorsements.cpp b/src/pal/test/verify_uvm_attestation_and_endorsements.cpp index bd8f694bef46..6c56e7112d7c 100644 --- a/src/pal/test/verify_uvm_attestation_and_endorsements.cpp +++ b/src/pal/test/verify_uvm_attestation_and_endorsements.cpp @@ -242,10 +242,13 @@ int main(int argc, char** argv) LOG_INFO_FMT("Verifying endorsements"); const auto attestation_unverified = - ccf::pal::snp::AttestationReport::from_unverified(quote_info.quote); + ccf::pal::snp::parse_attestation_report_unverified(quote_info.quote); validate_endorsements( endorsements, - attestation_unverified.reported_tcb(), + ccf::pal::snp::TcbVersionRaw::from_span( + ccf::pal::snp::get_report_bytes( + attestation_unverified.get(), + tav_snp_attestation_report_reported_tcb)), quote_info.endorsements); LOG_INFO_FMT("Verifying quote"); diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index 00f0881d827f..efea0555df06 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -966,33 +966,43 @@ namespace ccf static void trust_node_snp_tcb_version( ccf::kv::Tx& tx, const pal::snp::AttestationReport& attestation) { - if (attestation.version() < pal::snp::minimum_attestation_version) + if ( + tav_snp_attestation_report_version(attestation.get()) < + pal::snp::minimum_attestation_version) { throw std::logic_error(fmt::format( "SEV-SNP: attestation version {} is not supported. Minimum " "supported version is {}", - attestation.version(), + tav_snp_attestation_report_version(attestation.get()), pal::snp::minimum_attestation_version)); } // As cpuid -> attestation cpuid is surjective, we must use the local // cpuid and validate it against the attestation's cpuid auto cpuid = pal::snp::get_cpuid_untrusted(); if ( - cpuid.get_family_id() != attestation.cpuid_fam_id() || - cpuid.get_model_id() != attestation.cpuid_mod_id() || - cpuid.stepping != attestation.cpuid_step()) + cpuid.get_family_id() != + tav_snp_attestation_report_cpuid_fam_id(attestation.get()) || + cpuid.get_model_id() != + tav_snp_attestation_report_cpuid_mod_id(attestation.get()) || + cpuid.stepping != + tav_snp_attestation_report_cpuid_step(attestation.get())) { throw std::runtime_error(fmt::format( "CPU-sourced cpuid does not match attestation cpuid ({} != {}, {}, " "{})", cpuid.hex_str(), - attestation.cpuid_fam_id(), - attestation.cpuid_mod_id(), - attestation.cpuid_step())); + tav_snp_attestation_report_cpuid_fam_id(attestation.get()), + tav_snp_attestation_report_cpuid_mod_id(attestation.get()), + tav_snp_attestation_report_cpuid_step(attestation.get()))); } auto* h = tx.wo(Tables::SNP_TCB_VERSIONS); auto product = pal::snp::get_sev_snp_product(cpuid); - h->put(cpuid.hex_str(), attestation.reported_tcb().to_policy(product)); + h->put( + cpuid.hex_str(), + pal::snp::TcbVersionRaw::from_span( + pal::snp::get_report_bytes( + attestation.get(), tav_snp_attestation_report_reported_tcb)) + .to_policy(product)); } static void init_configuration( From bef2cfe72b950d755cccfa701cb1c09ac337a806 Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Thu, 10 Sep 2026 11:48:32 +0100 Subject: [PATCH 06/14] Inline SNP report byte accessors Replace get_report_bytes with direct TAV C calls and local spans. Document why report ownership needs the TAV deleter. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e97cc978-adf6-4072-bd60-c1d9b51096d8 --- include/ccf/pal/attestation_sev_snp.h | 46 +++---- src/js/extensions/snp_attestation.cpp | 116 ++++++++++++------ src/node/node_state.h | 18 +-- src/node/quote.cpp | 13 +- src/pal/attestation.cpp | 24 ++-- src/pal/test/snp_attestation_validation.cpp | 88 ++++++------- src/pal/test/snp_ioctl_test.cpp | 6 +- ...erify_uvm_attestation_and_endorsements.cpp | 9 +- src/service/internal_tables_access.h | 8 +- 9 files changed, 186 insertions(+), 142 deletions(-) diff --git a/include/ccf/pal/attestation_sev_snp.h b/include/ccf/pal/attestation_sev_snp.h index 541d2e023de3..fd741db64d91 100644 --- a/include/ccf/pal/attestation_sev_snp.h +++ b/include/ccf/pal/attestation_sev_snp.h @@ -442,6 +442,9 @@ 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 @@ -453,12 +456,8 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== using AttestationReport = std::unique_ptr; - using ReportBytesAccessor = - void (*)(const TavSnpAttestationReport*, const uint8_t**, size_t*); - - // The returned view borrows storage owned by the report handle. - inline std::span get_report_bytes( - const TavSnpAttestationReport* report, ReportBytesAccessor accessor) + inline std::span get_chip_id_for_vcek( + const AttestationReport& report) { if (report == nullptr) { @@ -466,15 +465,8 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== } const uint8_t* data = nullptr; size_t size = 0; - accessor(report, &data, &size); - return {data, size}; - } - - inline std::span get_chip_id_for_vcek( - const AttestationReport& report) - { - const auto chip_id = - get_report_bytes(report.get(), tav_snp_attestation_report_chip_id); + tav_snp_attestation_report_chip_id(report.get(), &data, &size); + const auto chip_id = std::span{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())); @@ -553,8 +545,12 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== auto chip_id_hex = 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 = - get_report_bytes(quote.get(), tav_snp_attestation_report_reported_tcb); + std::span{reported_tcb_data, reported_tcb_size}; uint64_t reported_tcb_value = 0; std::memcpy( &reported_tcb_value, reported_tcb_raw.data(), sizeof(reported_tcb_value)); @@ -613,12 +609,9 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== case ProductName::Milan: case ProductName::Genoa: { - auto tcb = - TcbVersionRaw::from_span( - get_report_bytes( - quote.get(), tav_snp_attestation_report_reported_tcb)) - .to_policy(product) - .to_milan_genoa(); + auto tcb = TcbVersionRaw::from_span(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); @@ -627,12 +620,9 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== } case ProductName::Turin: { - auto tcb = - TcbVersionRaw::from_span( - get_report_bytes( - quote.get(), tav_snp_attestation_report_reported_tcb)) - .to_policy(product) - .to_turin(); + auto tcb = TcbVersionRaw::from_span(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); diff --git a/src/js/extensions/snp_attestation.cpp b/src/js/extensions/snp_attestation.cpp index 97bfc02f6e32..ea84c11a1873 100644 --- a/src/js/extensions/snp_attestation.cpp +++ b/src/js/extensions/snp_attestation.cpp @@ -160,15 +160,21 @@ namespace ccf::js::extensions JS_CHECK_SET(a.set("policy", std::move(policy))); { - auto family_id = jsctx.new_array_buffer_copy(pal::snp::get_report_bytes( - attestation.get(), tav_snp_attestation_report_family_id)); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_family_id(attestation.get(), &data, &size); + auto family_id = + jsctx.new_array_buffer_copy(std::span{data, size}); JS_CHECK_EXC(family_id); JS_CHECK_SET(a.set("family_id", std::move(family_id))); } { - auto image_id = jsctx.new_array_buffer_copy(pal::snp::get_report_bytes( - attestation.get(), tav_snp_attestation_report_image_id)); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_image_id(attestation.get(), &data, &size); + auto image_id = + jsctx.new_array_buffer_copy(std::span{data, size}); JS_CHECK_EXC(image_id); JS_CHECK_SET(a.set("image_id", std::move(image_id))); } @@ -181,10 +187,12 @@ namespace ccf::js::extensions tav_snp_attestation_report_signature_algo(attestation.get())))); { - auto platform_version = jsctx.wrap(make_js_tcb_version( - jsctx, - pal::snp::get_report_bytes( - attestation.get(), tav_snp_attestation_report_platform_version))); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_platform_version( + attestation.get(), &data, &size); + auto platform_version = jsctx.wrap( + make_js_tcb_version(jsctx, std::span{data, size})); JS_CHECK_EXC(platform_version); JS_CHECK_SET(a.set("platform_version", std::move(platform_version))); } @@ -216,69 +224,88 @@ namespace ccf::js::extensions } { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_report_data(attestation.get(), &data, &size); auto attestation_report_data = - jsctx.new_array_buffer_copy(pal::snp::get_report_bytes( - attestation.get(), tav_snp_attestation_report_report_data)); + jsctx.new_array_buffer_copy(std::span{data, size}); JS_CHECK_EXC(attestation_report_data); JS_CHECK_SET(a.set("report_data", std::move(attestation_report_data))); } { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_measurement(attestation.get(), &data, &size); auto attestation_measurement = - jsctx.new_array_buffer_copy(pal::snp::get_report_bytes( - attestation.get(), tav_snp_attestation_report_measurement)); + jsctx.new_array_buffer_copy(std::span{data, size}); JS_CHECK_EXC(attestation_measurement); JS_CHECK_SET(a.set("measurement", std::move(attestation_measurement))); } { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_host_data(attestation.get(), &data, &size); auto attestation_host_data = - jsctx.new_array_buffer_copy(pal::snp::get_report_bytes( - attestation.get(), tav_snp_attestation_report_host_data)); + jsctx.new_array_buffer_copy(std::span{data, size}); JS_CHECK_EXC(attestation_host_data); JS_CHECK_SET(a.set("host_data", std::move(attestation_host_data))); } { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_id_key_digest( + attestation.get(), &data, &size); auto attestation_id_key_digest = - jsctx.new_array_buffer_copy(pal::snp::get_report_bytes( - attestation.get(), tav_snp_attestation_report_id_key_digest)); + jsctx.new_array_buffer_copy(std::span{data, size}); JS_CHECK_EXC(attestation_id_key_digest); JS_CHECK_SET( a.set("id_key_digest", std::move(attestation_id_key_digest))); } { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_author_key_digest( + attestation.get(), &data, &size); auto attestation_author_key_digest = - jsctx.new_array_buffer_copy(pal::snp::get_report_bytes( - attestation.get(), tav_snp_attestation_report_author_key_digest)); + jsctx.new_array_buffer_copy(std::span{data, size}); JS_CHECK_EXC(attestation_author_key_digest); JS_CHECK_SET( a.set("author_key_digest", std::move(attestation_author_key_digest))); } { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_report_id(attestation.get(), &data, &size); auto attestation_report_id = - jsctx.new_array_buffer_copy(pal::snp::get_report_bytes( - attestation.get(), tav_snp_attestation_report_report_id)); + jsctx.new_array_buffer_copy(std::span{data, size}); JS_CHECK_EXC(attestation_report_id); JS_CHECK_SET(a.set("report_id", std::move(attestation_report_id))); } { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_report_id_ma( + attestation.get(), &data, &size); auto attestation_report_id_ma = - jsctx.new_array_buffer_copy(pal::snp::get_report_bytes( - attestation.get(), tav_snp_attestation_report_report_id_ma)); + jsctx.new_array_buffer_copy(std::span{data, size}); JS_CHECK_EXC(attestation_report_id_ma); JS_CHECK_SET( a.set("report_id_ma", std::move(attestation_report_id_ma))); } { - auto reported_tcb = jsctx.wrap(make_js_tcb_version( - jsctx, - pal::snp::get_report_bytes( - attestation.get(), tav_snp_attestation_report_reported_tcb))); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_reported_tcb( + attestation.get(), &data, &size); + auto reported_tcb = jsctx.wrap( + make_js_tcb_version(jsctx, std::span{data, size})); JS_CHECK_EXC(reported_tcb); JS_CHECK_SET(a.set("reported_tcb", std::move(reported_tcb))); } @@ -294,18 +321,22 @@ namespace ccf::js::extensions tav_snp_attestation_report_cpuid_step(attestation.get()))); { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_chip_id(attestation.get(), &data, &size); auto attestation_chip_id = - jsctx.new_array_buffer_copy(pal::snp::get_report_bytes( - attestation.get(), tav_snp_attestation_report_chip_id)); + jsctx.new_array_buffer_copy(std::span{data, size}); JS_CHECK_EXC(attestation_chip_id); JS_CHECK_SET(a.set("chip_id", std::move(attestation_chip_id))); } { - auto committed_tcb = jsctx.wrap(make_js_tcb_version( - jsctx, - pal::snp::get_report_bytes( - attestation.get(), tav_snp_attestation_report_committed_tcb))); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_committed_tcb( + attestation.get(), &data, &size); + auto committed_tcb = jsctx.wrap( + make_js_tcb_version(jsctx, std::span{data, size})); JS_CHECK_EXC(committed_tcb); JS_CHECK_SET(a.set("committed_tcb", std::move(committed_tcb))); } @@ -330,10 +361,11 @@ namespace ccf::js::extensions tav_snp_attestation_report_committed_major(attestation.get()))); { - auto launch_tcb = jsctx.wrap(make_js_tcb_version( - jsctx, - pal::snp::get_report_bytes( - attestation.get(), tav_snp_attestation_report_launch_tcb))); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_launch_tcb(attestation.get(), &data, &size); + auto launch_tcb = jsctx.wrap( + make_js_tcb_version(jsctx, std::span{data, size})); JS_CHECK_EXC(launch_tcb); JS_CHECK_SET(a.set("launch_tcb", std::move(launch_tcb))); } @@ -342,17 +374,21 @@ namespace ccf::js::extensions JS_CHECK_EXC(signature); { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_signature_r(attestation.get(), &data, &size); auto signature_r = - jsctx.new_array_buffer_copy(pal::snp::get_report_bytes( - attestation.get(), tav_snp_attestation_report_signature_r)); + jsctx.new_array_buffer_copy(std::span{data, size}); JS_CHECK_EXC(signature_r); JS_CHECK_SET(signature.set("r", std::move(signature_r))); } { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_signature_s(attestation.get(), &data, &size); auto signature_s = - jsctx.new_array_buffer_copy(pal::snp::get_report_bytes( - attestation.get(), tav_snp_attestation_report_signature_s)); + jsctx.new_array_buffer_copy(std::span{data, size}); JS_CHECK_EXC(signature_s); JS_CHECK_SET(signature.set("s", std::move(signature_s))); } diff --git a/src/node/node_state.h b/src/node/node_state.h index 9b5f292b2cd9..f554aed89cb6 100644 --- a/src/node/node_state.h +++ b/src/node/node_state.h @@ -884,10 +884,11 @@ namespace ccf AttestationProvider::get_snp_attestation_report(quote_info); if (snp_attestation.has_value()) { - snp_tcb_version = ccf::pal::snp::TcbVersionRaw::from_span( - ccf::pal::snp::get_report_bytes( - snp_attestation.value().get(), - tav_snp_attestation_report_reported_tcb)); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_reported_tcb( + snp_attestation.value().get(), &data, &size); + snp_tcb_version = ccf::pal::snp::TcbVersionRaw::from_span({data, size}); } // Verify that the security policy matches the quoted digest of the policy @@ -1038,9 +1039,12 @@ namespace ccf const auto report = ccf::pal::snp::parse_attestation_report_unverified( quote_info.quote); - const auto reported_tcb = ccf::pal::snp::TcbVersionRaw::from_span( - ccf::pal::snp::get_report_bytes( - report.get(), tav_snp_attestation_report_reported_tcb)); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_reported_tcb( + report.get(), &data, &size); + const auto reported_tcb = + ccf::pal::snp::TcbVersionRaw::from_span({data, size}); // tcbm is a single hex value, like DB18000000000004. auto tcb_as_hex = reported_tcb.to_hex(); diff --git a/src/node/quote.cpp b/src/node/quote.cpp index 9a50fc99eaa4..3bdb7a89eb09 100644 --- a/src/node/quote.cpp +++ b/src/node/quote.cpp @@ -241,8 +241,10 @@ namespace ccf d, r, quote_info.endorsed_tcb); - const auto host_data = pal::snp::get_report_bytes( - report.get(), tav_snp_attestation_report_host_data); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_host_data(report.get(), &data, &size); + const auto host_data = std::span{data, size}; std::copy(host_data.begin(), host_data.end(), rep.begin()); } catch (const std::exception& e) @@ -343,10 +345,11 @@ namespace ccf auto product_family = pal::snp::get_sev_snp_product( tav_snp_attestation_report_cpuid_fam_id(attestation.get()), tav_snp_attestation_report_cpuid_mod_id(attestation.get())); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_reported_tcb(attestation.get(), &data, &size); auto attestation_tcb_policy = - pal::snp::TcbVersionRaw::from_span( - pal::snp::get_report_bytes( - attestation.get(), tav_snp_attestation_report_reported_tcb)) + pal::snp::TcbVersionRaw::from_span({data, size}) .to_policy(product_family); if (pal::snp::TcbVersionPolicy::is_valid( diff --git a/src/pal/attestation.cpp b/src/pal/attestation.cpp index dce1679fc571..01a405c7e169 100644 --- a/src/pal/attestation.cpp +++ b/src/pal/attestation.cpp @@ -356,15 +356,18 @@ namespace ccf::pal "enabled"); } + const uint8_t* reported_tcb_data = nullptr; + size_t reported_tcb_size = 0; + tav_snp_attestation_report_reported_tcb( + attestation.get(), &reported_tcb_data, &reported_tcb_size); + const auto reported_tcb_raw = + std::span{reported_tcb_data, reported_tcb_size}; auto endorsed_tcb = get_endorsed_tcb_from_cert(product_family, vcek_cert); if (endorsed_tcb.has_value()) { auto endorsed_tcb_policy = endorsed_tcb->to_policy(product_family); auto reported_tcb = - TcbVersionRaw::from_span( - snp::get_report_bytes( - attestation.get(), tav_snp_attestation_report_reported_tcb)) - .to_policy(product_family); + TcbVersionRaw::from_span(reported_tcb_raw).to_policy(product_family); if (!snp::TcbVersionPolicy::is_valid(endorsed_tcb_policy, reported_tcb)) { @@ -398,8 +401,7 @@ namespace ccf::pal auto raw_endorsed_tcb = snp::TcbVersionRaw::from_hex(std::string(claimed_endorsed_tcb.value())); - const auto reported_tcb = TcbVersionRaw::from_span(snp::get_report_bytes( - attestation.get(), tav_snp_attestation_report_reported_tcb)); + const auto reported_tcb = TcbVersionRaw::from_span(reported_tcb_raw); if (raw_endorsed_tcb != reported_tcb) { auto endorsed_tcb_hex = raw_endorsed_tcb.to_hex(); @@ -413,10 +415,12 @@ namespace ccf::pal // ---- Set return values ---- - report_data = SnpAttestationReportData(snp::get_report_bytes( - attestation.get(), tav_snp_attestation_report_report_data)); - measurement = SnpAttestationMeasurement(snp::get_report_bytes( - attestation.get(), tav_snp_attestation_report_measurement)); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_report_data(attestation.get(), &data, &size); + report_data = SnpAttestationReportData({data, size}); + tav_snp_attestation_report_measurement(attestation.get(), &data, &size); + measurement = SnpAttestationMeasurement({data, size}); return attestation; } diff --git a/src/pal/test/snp_attestation_validation.cpp b/src/pal/test/snp_attestation_validation.cpp index 724c34b3f941..6da5c3d6fd6c 100644 --- a/src/pal/test/snp_attestation_validation.cpp +++ b/src/pal/test/snp_attestation_validation.cpp @@ -151,19 +151,17 @@ TEST_CASE("unverified SNP report accessors") CHECK(tav_snp_attestation_report_version(report.get()) == 3); CHECK(tav_snp_attestation_report_cpuid_fam_id(report.get()) == 25); CHECK(tav_snp_attestation_report_cpuid_mod_id(report.get()) == 1); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_reported_tcb(report.get(), &data, &size); CHECK( - snp::TcbVersionRaw::from_span( - ccf::pal::snp::get_report_bytes( - report.get(), tav_snp_attestation_report_reported_tcb)) - .to_hex() == "db18000000000004"); - const auto measurement = ccf::pal::snp::get_report_bytes( - report.get(), tav_snp_attestation_report_measurement); + snp::TcbVersionRaw::from_span({data, size}).to_hex() == "db18000000000004"); + tav_snp_attestation_report_measurement(report.get(), &data, &size); + const auto measurement = std::span{data, size}; auto moved_report = std::move(report); CHECK(measurement.size() == snp_attestation_measurement_size); - CHECK( - ccf::pal::snp::get_report_bytes( - moved_report.get(), tav_snp_attestation_report_signature_r) - .size() == 72); + tav_snp_attestation_report_signature_r(moved_report.get(), &data, &size); + CHECK(size == 72); } TEST_CASE("CCF policy is separate from generic TAV verification") @@ -221,7 +219,7 @@ TEST_CASE("SNP byte accessors borrow report storage") { struct ByteField { - ccf::pal::snp::ReportBytesAccessor accessor; + void (*accessor)(const TavSnpAttestationReport*, const uint8_t**, size_t*); size_t offset; size_t size; }; @@ -247,20 +245,25 @@ TEST_CASE("SNP byte accessors borrow report storage") auto report = ccf::pal::snp::parse_attestation_report_unverified(raw_report); for (const auto& [accessor, offset, size] : fields) { - const auto first = ccf::pal::snp::get_report_bytes(report.get(), accessor); - const auto second = ccf::pal::snp::get_report_bytes(report.get(), accessor); + const uint8_t* data = nullptr; + size_t length = 0; + accessor(report.get(), &data, &length); + const auto first = std::span{data, length}; + data = nullptr; + length = 0; + accessor(report.get(), &data, &length); + const auto second = std::span{data, length}; CHECK(first.data() == second.data()); REQUIRE(first.size() == size); CHECK(std::equal(first.begin(), first.end(), raw_report.begin() + offset)); } } -TEST_CASE("SNP borrowed byte access rejects empty handles") +TEST_CASE("SNP chip ID access rejects empty handles") { ccf::pal::snp::AttestationReport report; CHECK_THROWS_WITH_AS( - ccf::pal::snp::get_report_bytes( - report.get(), tav_snp_attestation_report_measurement), + ccf::pal::snp::get_chip_id_for_vcek(report), "Cannot access an empty SNP attestation report", std::logic_error); } @@ -271,27 +274,25 @@ TEST_CASE("SNP borrowed bytes survive report ownership transfers") auto raw_report = testing::milan_attestation; std::optional original = parse_attestation_report_unverified(raw_report); - const auto measurement = ccf::pal::snp::get_report_bytes( - original->get(), tav_snp_attestation_report_measurement); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_measurement(original->get(), &data, &size); + const auto measurement = std::span{data, size}; raw_report[0x090] ^= 0xff; CHECK(measurement[0] == testing::milan_attestation[0x090]); auto moved = std::move(*original); original.reset(); - CHECK( - measurement.data() == - ccf::pal::snp::get_report_bytes( - moved.get(), tav_snp_attestation_report_measurement) - .data()); + tav_snp_attestation_report_measurement(moved.get(), &data, &size); + CHECK(measurement.data() == data); + CHECK(measurement.size() == size); auto assigned = parse_attestation_report_unverified(testing::genoa_attestation); assigned = std::move(moved); - CHECK( - measurement.data() == - ccf::pal::snp::get_report_bytes( - assigned.get(), tav_snp_attestation_report_measurement) - .data()); + tav_snp_attestation_report_measurement(assigned.get(), &data, &size); + CHECK(measurement.data() == data); + CHECK(measurement.size() == size); CHECK(std::equal( measurement.begin(), measurement.end(), @@ -307,8 +308,10 @@ TEST_CASE("VCEK chip ID borrows the product-specific prefix") &testing::turin_attestation}) { auto report = parse_attestation_report_unverified(*raw_report); - const auto chip_id = ccf::pal::snp::get_report_bytes( - report.get(), tav_snp_attestation_report_chip_id); + 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{data, size}; const auto vcek_chip_id = get_chip_id_for_vcek(report); CHECK(vcek_chip_id.data() == chip_id.data()); CHECK( @@ -637,12 +640,13 @@ TEST_CASE("Parsing tcb versions from attestaion") { auto milan_attestation = ccf::pal::snp::parse_attestation_report_unverified( ccf::pal::snp::testing::milan_attestation); - auto milan_tcb = - ccf::pal::snp::TcbVersionRaw::from_span( - ccf::pal::snp::get_report_bytes( - milan_attestation.get(), tav_snp_attestation_report_reported_tcb)) - .to_policy(ccf::pal::snp::ProductName::Milan) - .to_milan_genoa(); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_reported_tcb( + milan_attestation.get(), &data, &size); + auto milan_tcb = ccf::pal::snp::TcbVersionRaw::from_span({data, size}) + .to_policy(ccf::pal::snp::ProductName::Milan) + .to_milan_genoa(); CHECK_EQ(milan_tcb.microcode, 0xdb); CHECK_EQ(milan_tcb.snp, 0x18); CHECK_EQ(milan_tcb.tee, 0x00); @@ -916,17 +920,17 @@ TEST_CASE("Extracting metadata from endorsements") auto endorsed_tcb = pal::get_endorsed_tcb_from_cert( pal::snp::ProductName::Milan, chip_certificate); REQUIRE(endorsed_tcb.has_value()); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_reported_tcb(attestation.get(), &data, &size); CHECK_EQ( nlohmann::json(endorsed_tcb.value()).dump(), - nlohmann::json( - pal::snp::TcbVersionRaw::from_span(ccf::pal::snp::get_report_bytes( - attestation.get(), tav_snp_attestation_report_reported_tcb))) - .dump()); + nlohmann::json(pal::snp::TcbVersionRaw::from_span({data, size})).dump()); auto endorsed_chip_id = pal::get_endorsed_chip_id_from_cert(chip_certificate); REQUIRE(endorsed_chip_id.has_value()); - auto printable_reported_chip_id = ccf::pal::snp::get_report_bytes( - attestation.get(), tav_snp_attestation_report_chip_id); + tav_snp_attestation_report_chip_id(attestation.get(), &data, &size); + const auto printable_reported_chip_id = std::span{data, size}; CHECK_EQ( ds::to_hex(endorsed_chip_id.value()), ds::to_hex(printable_reported_chip_id)); diff --git a/src/pal/test/snp_ioctl_test.cpp b/src/pal/test/snp_ioctl_test.cpp index 2ec67a41e274..407b41b88a69 100644 --- a/src/pal/test/snp_ioctl_test.cpp +++ b/src/pal/test/snp_ioctl_test.cpp @@ -28,8 +28,10 @@ TEST_CASE("SNP request attestation") const auto attestation = snp::parse_attestation_report_unverified(report_bytes); - SnpAttestationReportData attested_report_data(snp::get_report_bytes( - attestation.get(), tav_snp_attestation_report_report_data)); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_report_data(attestation.get(), &data, &size); + SnpAttestationReportData attested_report_data({data, size}); REQUIRE_EQ(snp_report_data.report_data, attested_report_data.report_data); } diff --git a/src/pal/test/verify_uvm_attestation_and_endorsements.cpp b/src/pal/test/verify_uvm_attestation_and_endorsements.cpp index 6c56e7112d7c..6932390b1972 100644 --- a/src/pal/test/verify_uvm_attestation_and_endorsements.cpp +++ b/src/pal/test/verify_uvm_attestation_and_endorsements.cpp @@ -243,12 +243,13 @@ int main(int argc, char** argv) LOG_INFO_FMT("Verifying endorsements"); const auto attestation_unverified = ccf::pal::snp::parse_attestation_report_unverified(quote_info.quote); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_reported_tcb( + attestation_unverified.get(), &data, &size); validate_endorsements( endorsements, - ccf::pal::snp::TcbVersionRaw::from_span( - ccf::pal::snp::get_report_bytes( - attestation_unverified.get(), - tav_snp_attestation_report_reported_tcb)), + ccf::pal::snp::TcbVersionRaw::from_span({data, size}), quote_info.endorsements); LOG_INFO_FMT("Verifying quote"); diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index efea0555df06..1ac661ad2a30 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -997,12 +997,12 @@ namespace ccf } auto* h = tx.wo(Tables::SNP_TCB_VERSIONS); auto product = pal::snp::get_sev_snp_product(cpuid); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_reported_tcb(attestation.get(), &data, &size); h->put( cpuid.hex_str(), - pal::snp::TcbVersionRaw::from_span( - pal::snp::get_report_bytes( - attestation.get(), tav_snp_attestation_report_reported_tcb)) - .to_policy(product)); + pal::snp::TcbVersionRaw::from_span({data, size}).to_policy(product)); } static void init_configuration( From 50c9bee4572bd9cfe9f185fd19535f23201a71a9 Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Thu, 10 Sep 2026 13:09:29 +0100 Subject: [PATCH 07/14] chkpt --- CHANGELOG.md | 3 +- include/ccf/pal/attestation.h | 13 +-- include/ccf/pal/attestation_sev_snp.h | 64 ++++++++------ include/ccf/pal/snp_ioctl6.h | 50 +++++------ src/js/extensions/snp_attestation.cpp | 8 +- src/node/quote.cpp | 19 ++--- src/pal/attestation.cpp | 65 +++++--------- src/pal/test/snp_attestation_validation.cpp | 95 +++++++++++++++------ 8 files changed, 162 insertions(+), 155 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f3dce7bada9..b1efab9db0c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,12 +12,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Fixed - Fixed swapped `current_minor` and `current_build` values in JavaScript `verifySnpAttestation()` results. These fields now match the AMD SEV-SNP report layout. The deprecated C++ `ccf::pal::snp::Attestation` retains its previous field mapping for compatibility. (#8083) -- The exported `ccf_rs` CMake target now supplies its OpenSSL link dependencies, so downstream consumers no longer need to add them manually. (#8083) - Transactions from an earlier view are now rejected before entering the replication queue even after the node has stepped down. This prevents rolled-back writes from being replicated after a later election and blocking subsequent replication (#8293, #8295). ### Changed -- SNP attestation reports now use TAV's C API with `ccf::pal::snp::AttestationReport` as an owning smart-pointer alias. Use `parse_attestation_report_unverified()` to decode reports and `verify_attestation_report()` to apply TAV verification and CCF's policy. TAV byte accessors borrow report storage; destroying or replacing the owner invalidates those views. The packed `ccf::pal::snp::Attestation` wire-layout type and its legacy accessors remain available but are deprecated. (#8083) +- SNP attestation reports now use TAV's C API with `ccf::pal::snp::AttestationReport` as an owning smart-pointer alias. Use `parse_attestation_report_unverified()` to decode reports and `ccf::pal::verify_snp_attestation_report_and_get()` to apply TAV verification and CCF's policy. TAV byte accessors borrow report storage; destroying or replacing the owner invalidates those views. The packed `ccf::pal::snp::Attestation` wire-layout type and its legacy accessors remain available but are deprecated. (#8083) - `ccf::pal::snp::get_attestation_bytes()` in `ccf/pal/snp_ioctl.h` requests an unverified SNP report as owned bytes, without using the legacy report type. `AttestationInterface::get_raw()` and its ioctl implementation remain available but are deprecated. (#8083) - CBOR parsing now rejects composite (array or map) and tagged values used as map keys anywhere in the decoded document, including nested maps in optional COSE headers (#8297). diff --git a/include/ccf/pal/attestation.h b/include/ccf/pal/attestation.h index 34eeb7306111..d3f1fbb7e4ee 100644 --- a/include/ccf/pal/attestation.h +++ b/include/ccf/pal/attestation.h @@ -29,18 +29,7 @@ namespace ccf::pal PlatformAttestationMeasurement& measurement, PlatformAttestationReportData& report_data); - namespace snp - { - /// Verify with TAV, then enforce CCF's SNP attestation policy. - AttestationReport verify_attestation_report( - std::span report, - std::span endorsements, - PlatformAttestationMeasurement& measurement, - PlatformAttestationReportData& report_data, - std::optional endorsed_tcb = std::nullopt); - } - - [[deprecated("Use snp::verify_attestation_report")]] + /// Verify with TAV, then enforce CCF's SNP attestation policy. snp::AttestationReport verify_snp_attestation_report_and_get( const QuoteInfo& quote_info, PlatformAttestationMeasurement& measurement, diff --git a/include/ccf/pal/attestation_sev_snp.h b/include/ccf/pal/attestation_sev_snp.h index fd741db64d91..dbc39ea73e5c 100644 --- a/include/ccf/pal/attestation_sev_snp.h +++ b/include/ccf/pal/attestation_sev_snp.h @@ -31,8 +31,9 @@ namespace ccf::pal::snp static constexpr auto NO_SECURITY_POLICY = ""; + // From https://developer.amd.com/sev/ [[deprecated("TAV verifies AMD root signing keys internally")]] - inline constexpr auto amd_milan_root_signing_public_key = + constexpr auto amd_milan_root_signing_public_key = R"(-----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0Ld52RJOdeiJlqK2JdsV mD7FktuotWwX1fNgW41XY9Xz1HEhSUmhLz9Cu9DHRlvgJSNxbeYYsnJfvyjx1MfU @@ -49,7 +50,7 @@ QPHfbkH0CyPfhl1jWhJFZasCAwEAAQ== -----END PUBLIC KEY----- )"; [[deprecated("TAV verifies AMD root signing keys internally")]] - inline constexpr auto amd_genoa_root_signing_public_key = + constexpr auto amd_genoa_root_signing_public_key = R"(-----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA3Cd95S/uFOuRIskW9vz9 VDBF69NDQF79oRhL/L2PVQGhK3YdfEBgpF/JiwWFBsT/fXDhzA01p3LkcT/7Ldjc @@ -66,7 +67,7 @@ HP1qYrnvhzaG1S70vw6OkbaaC9EjiH/uHgAJQGxon7u0Q7xgoREWA/e7JcBQwLg8 -----END PUBLIC KEY----- )"; [[deprecated("TAV verifies AMD root signing keys internally")]] - inline constexpr auto amd_turin_root_signing_public_key = + constexpr auto amd_turin_root_signing_public_key = R"(-----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwaAriB7EIuVc4ZB1wD3Y fDxL+9eyS7+izm0Jj3W772NINCWl8Bj3w/JD2ZjmbRxWdIq/4d9iarCKorXloJUB @@ -325,6 +326,25 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== static_assert( sizeof(TcbVersionRaw) == snp_tcb_version_size, "TCB version raw size mismatch"); +#pragma pack(push, 1) + inline void to_json(nlohmann::json& j, const TcbVersionRaw& tcb_version) + { + j = tcb_version.to_hex(); + } + inline void from_json(const nlohmann::json& j, TcbVersionRaw& tcb_version_raw) + { + if (!j.is_string()) + { + throw std::logic_error( + fmt::format("Invalid TCB version raw data: {}", j.dump())); + } + tcb_version_raw = TcbVersionRaw::from_hex(j.get()); + } + inline std::string schema_name(const TcbVersionRaw& tcb_version) + { + (void)tcb_version; + return "TcbVersionRaw"; + } struct Signature { @@ -332,7 +352,9 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== uint8_t s[72]; uint8_t reserved[512 - 144]; }; +#pragma pack(pop) + // Table 105 // NOLINTNEXTLINE(performance-enum-size) enum class SignatureAlgorithm : uint32_t { @@ -341,6 +363,7 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== }; #pragma pack(push, 1) + // Table 8 struct GuestPolicy { uint8_t abi_minor; @@ -357,6 +380,8 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== sizeof(GuestPolicy) == sizeof(uint64_t), "Cannot cast GuestPolicy to uint64_t"); + static constexpr uint8_t attestation_flags_signing_key_vcek = 0; + #pragma pack(push, 1) struct Flags { @@ -370,6 +395,7 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== sizeof(Flags) == sizeof(uint32_t), "Cannot cast Flags to uint32_t"); #pragma pack(push, 1) + // Table 22 struct PlatformInfo { uint8_t smt_en : 1; @@ -386,6 +412,11 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== struct [[deprecated("Use ccf::pal::snp::AttestationReport")]] Attestation; #pragma pack(push, 1) + // Table 21 + + static constexpr uint32_t minimum_attestation_version = 3; + static constexpr uint32_t attestation_policy_abi_major = 1; + struct Attestation { uint32_t version = 0; /* 0x000 */ @@ -407,7 +438,7 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== uint8_t report_id[32] = {0}; /* 0x140 */ uint8_t report_id_ma[32] = {0}; /* 0x160 */ TcbVersionRaw reported_tcb; /* 0x180 */ - uint8_t cpuid_fam_id = 0; /* 0x188 */ + uint8_t cpuid_fam_id = 0; /* 0x188*/ uint8_t cpuid_mod_id = 0; /* 0x189 */ uint8_t cpuid_step = 0; /* 0x18A */ uint8_t reserved1[21] = {0}; /* 0x18B */ @@ -432,6 +463,8 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== { 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}; @@ -485,29 +518,6 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== [[nodiscard]] AttestationReport parse_attestation_report_unverified( std::span report); - inline void to_json(nlohmann::json& j, const TcbVersionRaw& tcb_version) - { - j = tcb_version.to_hex(); - } - inline void from_json(const nlohmann::json& j, TcbVersionRaw& tcb_version_raw) - { - if (!j.is_string()) - { - throw std::logic_error( - fmt::format("Invalid TCB version raw data: {}", j.dump())); - } - tcb_version_raw = TcbVersionRaw::from_hex(j.get()); - } - inline std::string schema_name(const TcbVersionRaw& tcb_version) - { - (void)tcb_version; - return "TcbVersionRaw"; - } - - static constexpr uint8_t attestation_flags_signing_key_vcek = 0; - static constexpr uint32_t minimum_attestation_version = 3; - static constexpr uint32_t attestation_policy_abi_major = 1; - static HostPort get_endpoint_loc( const EndorsementsServer& server, const HostPort& default_values) { diff --git a/include/ccf/pal/snp_ioctl6.h b/include/ccf/pal/snp_ioctl6.h index 6261187f58dd..136d64d44ce4 100644 --- a/include/ccf/pal/snp_ioctl6.h +++ b/include/ccf/pal/snp_ioctl6.h @@ -26,15 +26,26 @@ namespace ccf::pal::snp::ioctl6 namespace detail { + // Linux snp_guest_msg is 4096 bytes: a 96-byte outer message header and + // 4000-byte payload. The ioctl returns only the decrypted payload, with + // its own 32-byte report response header before the report. + // https://github.com/torvalds/linux/blob/v6.8/drivers/virt/coco/sev-guest/sev-guest.h + // https://github.com/torvalds/linux/blob/v6.8/include/uapi/linux/sev-guest.h constexpr size_t ATTESTATION_RESPONSE_SIZE = 4000; - constexpr size_t REPORT_SIZE_OFFSET = sizeof(uint32_t); - constexpr size_t REPORT_OFFSET = 0x20; - using AttestationResponseBytes = - std::array; - static_assert( - sizeof(AttestationResponseBytes) == ATTESTATION_RESPONSE_SIZE); - static_assert( - REPORT_OFFSET + attestation_report_size <= ATTESTATION_RESPONSE_SIZE); + struct AttestationResponse + { + uint32_t status = 0; + uint32_t report_size = 0; + std::array reserved = {}; + std::array report = {}; + std::array< + uint8_t, + ATTESTATION_RESPONSE_SIZE - 0x20 - attestation_report_size> + padding = {}; + }; + static_assert(sizeof(AttestationResponse) == ATTESTATION_RESPONSE_SIZE); + static_assert(offsetof(AttestationResponse, report_size) == 0x04); + static_assert(offsetof(AttestationResponse, report) == 0x20); } #pragma pack(push, 1) @@ -137,9 +148,8 @@ namespace ccf::pal::snp::ioctl6 // padding to the size of SEV_SNP_REPORT_RSP_BUF_SZ (i.e., 1280 bytes) }; #pragma pack(pop) - static_assert( - offsetof(AttestationResp, report_size) == detail::REPORT_SIZE_OFFSET); - static_assert(offsetof(AttestationResp, report) == detail::REPORT_OFFSET); + static_assert(offsetof(AttestationResp, report_size) == 0x04); + static_assert(offsetof(AttestationResp, report) == 0x20); static_assert(sizeof(AttestationResp) == 1280); #pragma GCC diagnostic pop @@ -217,7 +227,7 @@ namespace ccf::pal::snp::ioctl6 namespace detail { using GuestRequestAttestationBytes = - GuestRequest; + GuestRequest; static_assert( sizeof(GuestRequestAttestationBytes) == sizeof(GuestRequestAttestation)); } @@ -251,17 +261,10 @@ namespace ccf::pal::snp::ioctl6 } inline std::vector extract_attestation_bytes( - const AttestationResponseBytes& response) + const AttestationResponse& response) { - uint32_t report_size = 0; - std::memcpy( - &report_size, - response.data() + REPORT_SIZE_OFFSET, - sizeof(report_size)); - validate_report_size(report_size); - return { - response.begin() + REPORT_OFFSET, - response.begin() + REPORT_OFFSET + attestation_report_size}; + validate_report_size(response.report_size); + return {response.report.begin(), response.report.end()}; } template @@ -321,8 +324,7 @@ namespace ccf::pal::snp::ioctl6 static std::vector get_attestation_bytes( const PlatformAttestationReportData& report_data) { - IoctlSentinel response; - response.data.fill(0); + IoctlSentinel response; detail::request_attestation(report_data, response); return detail::extract_attestation_bytes(response.data); } diff --git a/src/js/extensions/snp_attestation.cpp b/src/js/extensions/snp_attestation.cpp index ea84c11a1873..9e8903e472f1 100644 --- a/src/js/extensions/snp_attestation.cpp +++ b/src/js/extensions/snp_attestation.cpp @@ -104,12 +104,8 @@ namespace ccf::js::extensions try { - verified_attestation.emplace(pal::snp::verify_attestation_report( - quote_info.quote, - quote_info.endorsements, - measurement, - report_data, - quote_info.endorsed_tcb)); + verified_attestation.emplace(pal::verify_snp_attestation_report_and_get( + quote_info, measurement, report_data)); if (uvm_endorsements.has_value()) { parsed_uvm_endorsements = diff --git a/src/node/quote.cpp b/src/node/quote.cpp index 3bdb7a89eb09..1b6d6eb65b91 100644 --- a/src/node/quote.cpp +++ b/src/node/quote.cpp @@ -165,12 +165,7 @@ namespace ccf { pal::PlatformAttestationMeasurement d = {}; pal::PlatformAttestationReportData r = {}; - return pal::snp::verify_attestation_report( - quote_info.quote, - quote_info.endorsements, - d, - r, - quote_info.endorsed_tcb); + return pal::verify_snp_attestation_report_and_get(quote_info, d, r); } catch (const std::exception& e) { @@ -235,12 +230,8 @@ namespace ccf pal::PlatformAttestationReportData r = {}; try { - const auto report = pal::snp::verify_attestation_report( - quote_info.quote, - quote_info.endorsements, - d, - r, - quote_info.endorsed_tcb); + const auto report = + pal::verify_snp_attestation_report_and_get(quote_info, d, r); const uint8_t* data = nullptr; size_t size = 0; tav_snp_attestation_report_host_data(report.get(), &data, &size); @@ -313,8 +304,8 @@ namespace ccf pal::PlatformAttestationMeasurement d = {}; pal::PlatformAttestationReportData r = {}; - auto attestation = pal::snp::verify_attestation_report( - quote_info.quote, quote_info.endorsements, d, r, quote_info.endorsed_tcb); + auto attestation = + pal::verify_snp_attestation_report_and_get(quote_info, d, r); std::optional min_tcb_opt = std::nullopt; auto* h = tx.ro(Tables::SNP_TCB_VERSIONS); diff --git a/src/pal/attestation.cpp b/src/pal/attestation.cpp index 01a405c7e169..2e365f56957d 100644 --- a/src/pal/attestation.cpp +++ b/src/pal/attestation.cpp @@ -245,13 +245,20 @@ namespace ccf::pal } // Verifying SNP attestation report is available on all platforms. - snp::AttestationReport snp::verify_attestation_report( - std::span report, - std::span endorsements, + snp::AttestationReport verify_snp_attestation_report_and_get( + const QuoteInfo& quote_info, PlatformAttestationMeasurement& measurement, - PlatformAttestationReportData& report_data, - std::optional claimed_endorsed_tcb) + PlatformAttestationReportData& report_data) { + if (quote_info.format != QuoteFormat::amd_sev_snp_v1) + { + throw std::logic_error(fmt::format( + "Unexpected attestation report to verify for SEV-SNP: {}", + quote_info.format)); + } + + const auto& report = quote_info.quote; + const auto& endorsements = quote_info.endorsements; if (report.size() != snp::attestation_report_size) { throw std::logic_error(fmt::format( @@ -289,7 +296,7 @@ namespace ccf::pal vcek_cert.size(), &raw_report), tav_error_free); - AttestationReport attestation(raw_report); + snp::AttestationReport attestation(raw_report); check_tav_error("verification", error.get()); if (attestation == nullptr) { @@ -366,8 +373,8 @@ namespace ccf::pal if (endorsed_tcb.has_value()) { auto endorsed_tcb_policy = endorsed_tcb->to_policy(product_family); - auto reported_tcb = - TcbVersionRaw::from_span(reported_tcb_raw).to_policy(product_family); + auto reported_tcb = snp::TcbVersionRaw::from_span(reported_tcb_raw) + .to_policy(product_family); if (!snp::TcbVersionPolicy::is_valid(endorsed_tcb_policy, reported_tcb)) { @@ -380,7 +387,7 @@ namespace ccf::pal } auto endorsed_chip_id = get_endorsed_chip_id_from_cert(vcek_cert); - auto reported_chip_id = get_chip_id_for_vcek(attestation); + auto reported_chip_id = snp::get_chip_id_for_vcek(attestation); if ( endorsed_chip_id.has_value() && (endorsed_chip_id->size() != reported_chip_id.size() || @@ -396,12 +403,12 @@ namespace ccf::pal ccf::ds::to_hex(reported_chip_id))); } - if (claimed_endorsed_tcb.has_value()) + if (quote_info.endorsed_tcb.has_value()) { auto raw_endorsed_tcb = - snp::TcbVersionRaw::from_hex(std::string(claimed_endorsed_tcb.value())); + snp::TcbVersionRaw::from_hex(quote_info.endorsed_tcb.value()); - const auto reported_tcb = TcbVersionRaw::from_span(reported_tcb_raw); + const auto reported_tcb = snp::TcbVersionRaw::from_span(reported_tcb_raw); if (raw_endorsed_tcb != reported_tcb) { auto endorsed_tcb_hex = raw_endorsed_tcb.to_hex(); @@ -424,44 +431,12 @@ namespace ccf::pal return attestation; } - snp::AttestationReport verify_snp_attestation_report_and_get( - const QuoteInfo& quote_info, - PlatformAttestationMeasurement& measurement, - PlatformAttestationReportData& report_data) - { - if (quote_info.format != QuoteFormat::amd_sev_snp_v1) - { - throw std::logic_error(fmt::format( - "Unexpected attestation report to verify for SEV-SNP: {}", - quote_info.format)); - } - - return snp::verify_attestation_report( - quote_info.quote, - quote_info.endorsements, - measurement, - report_data, - quote_info.endorsed_tcb); - } - void verify_snp_attestation_report( const QuoteInfo& quote_info, PlatformAttestationMeasurement& measurement, PlatformAttestationReportData& report_data) { - if (quote_info.format != QuoteFormat::amd_sev_snp_v1) - { - throw std::logic_error(fmt::format( - "Unexpected attestation report to verify for SEV-SNP: {}", - quote_info.format)); - } - - snp::verify_attestation_report( - quote_info.quote, - quote_info.endorsements, - measurement, - report_data, - quote_info.endorsed_tcb); + verify_snp_attestation_report_and_get(quote_info, measurement, report_data); } void verify_quote( diff --git a/src/pal/test/snp_attestation_validation.cpp b/src/pal/test/snp_attestation_validation.cpp index 6da5c3d6fd6c..8013203834b1 100644 --- a/src/pal/test/snp_attestation_validation.cpp +++ b/src/pal/test/snp_attestation_validation.cpp @@ -194,13 +194,14 @@ TEST_CASE("CCF policy is separate from generic TAV verification") const std::vector endorsements( snp::testing::milan_endorsements.begin(), snp::testing::milan_endorsements.end()); + const ccf::QuoteInfo quote_info = { + .format = ccf::QuoteFormat::amd_sev_snp_v1, + .quote = snp::testing::milan_attestation, + .endorsements = endorsements, + .uvm_endorsements = std::nullopt, + .endorsed_tcb = "0000000000000000"}; CHECK_THROWS_WITH_AS( - snp::verify_attestation_report( - snp::testing::milan_attestation, - endorsements, - measurement, - report_data, - "0000000000000000"), + verify_snp_attestation_report_and_get(quote_info, measurement, report_data), doctest::Contains("does not match reported TCB"), std::logic_error); } @@ -344,14 +345,46 @@ TEST_CASE("SNP verification preserves invalid size error") { ccf::pal::PlatformAttestationMeasurement measurement; ccf::pal::PlatformAttestationReportData report_data; + const ccf::QuoteInfo quote_info = { + .format = ccf::QuoteFormat::amd_sev_snp_v1, + .quote = std::vector(100), + .endorsements = {}, + .uvm_endorsements = std::nullopt}; CHECK_THROWS_WITH_AS( - ccf::pal::snp::verify_attestation_report( - std::vector(100), {}, measurement, report_data), + ccf::pal::verify_snp_attestation_report_and_get( + quote_info, measurement, report_data), doctest::Contains( "Input SEV-SNP attestation report is not of expected size 1184: 100"), std::logic_error); } +TEST_CASE("SNP verification rejects other quote formats before parsing") +{ + for (const auto format : + {ccf::QuoteFormat::insecure_virtual, ccf::QuoteFormat::oe_sgx_v1}) + { + const ccf::QuoteInfo quote_info = { + .format = format, + .quote = {}, + .endorsements = {}, + .uvm_endorsements = std::nullopt}; + ccf::pal::PlatformAttestationMeasurement measurement; + ccf::pal::PlatformAttestationReportData report_data; + const auto expected_error = fmt::format( + "Unexpected attestation report to verify for SEV-SNP: {}", format); + CHECK_THROWS_WITH_AS( + ccf::pal::verify_snp_attestation_report_and_get( + quote_info, measurement, report_data), + expected_error.c_str(), + std::logic_error); + CHECK_THROWS_WITH_AS( + ccf::pal::verify_snp_attestation_report( + quote_info, measurement, report_data), + expected_error.c_str(), + std::logic_error); + } +} + TEST_CASE("SNP ioctl response bytes exclude response headers and padding") { using namespace ccf::pal::snp; @@ -361,23 +394,34 @@ TEST_CASE("SNP ioctl response bytes exclude response headers and padding") std::declval())), std::vector>); - ioctl6::IoctlSentinel response; - response.data.fill(0xa5); - const uint32_t report_size = attestation_report_size; - std::memcpy( - response.data.data() + ioctl6::detail::REPORT_SIZE_OFFSET, - &report_size, - sizeof(report_size)); + using Response = ioctl6::detail::AttestationResponse; + static_assert(sizeof(Response) == 4000); + static_assert(offsetof(Response, status) == 0); + static_assert(offsetof(Response, report_size) == 4); + static_assert(offsetof(Response, reserved) == 8); + static_assert(offsetof(Response, report) == 0x20); + static_assert(offsetof(Response, padding) == 0x20 + attestation_report_size); + + ioctl6::IoctlSentinel response; + CHECK(response.data.status == 0); + CHECK(response.data.report_size == 0); + CHECK(response.data.reserved == decltype(response.data.reserved){}); + CHECK(response.data.report == decltype(response.data.report){}); + CHECK(response.data.padding == decltype(response.data.padding){}); + response.data.status = 0xa5a5a5a5; + response.data.report_size = attestation_report_size; + response.data.reserved.fill(0xa5); + response.data.padding.fill(0xa5); std::copy( testing::milan_attestation.begin(), testing::milan_attestation.end(), - response.data.begin() + ioctl6::detail::REPORT_OFFSET); + response.data.report.begin()); const auto report_bytes = ioctl6::detail::extract_attestation_bytes(response.data); CHECK(report_bytes == testing::milan_attestation); CHECK(response.sentinels_intact()); - response.data.fill(0); + response.data = {}; CHECK(report_bytes == testing::milan_attestation); response.post_sentinels[1] ^= 1; @@ -387,14 +431,11 @@ TEST_CASE("SNP ioctl response bytes exclude response headers and padding") TEST_CASE("SNP ioctl response bytes reject invalid report sizes") { using namespace ccf::pal::snp; - ioctl6::detail::AttestationResponseBytes response = {}; + ioctl6::detail::AttestationResponse response = {}; for (const uint32_t report_size : {0U, 1183U, 1185U, std::numeric_limits::max()}) { - std::memcpy( - response.data() + ioctl6::detail::REPORT_SIZE_OFFSET, - &report_size, - sizeof(report_size)); + response.report_size = report_size; const auto expected_error = fmt::format( "Unexpected SEV-SNP attestation report size: {} != {}", report_size, @@ -482,18 +523,22 @@ TEST_CASE("milan validation") pal::PlatformAttestationMeasurement measurement; pal::PlatformAttestationReportData report_data; -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" static_assert(std::is_same_v< decltype(pal::verify_snp_attestation_report_and_get( std::declval(), std::declval(), std::declval())), pal::snp::AttestationReport>); -#pragma GCC diagnostic pop + const auto report = pal::verify_snp_attestation_report_and_get( + milan_quote_info, measurement, report_data); + REQUIRE(report != nullptr); + const auto verified_measurement = measurement.data; + const auto verified_report_data = report_data.data; pal::verify_snp_attestation_report( milan_quote_info, measurement, report_data); + CHECK(measurement.data == verified_measurement); + CHECK(report_data.data == verified_report_data); } TEST_CASE("genoa validation") From 4fe2b9d100a4c2139b1106bc38cfa2533525753e Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Thu, 10 Sep 2026 13:58:00 +0100 Subject: [PATCH 08/14] chkpt --- CHANGELOG.md | 4 +- include/ccf/pal/attestation_sev_snp.h | 17 ++- include/ccf/pal/snp_ioctl.h | 8 +- include/ccf/pal/snp_ioctl6.h | 74 +++++++------ src/pal/quote_generation.h | 7 +- src/pal/test/snp_attestation_validation.cpp | 109 ++++++++++++++++---- src/pal/test/snp_ioctl_test.cpp | 9 +- 7 files changed, 154 insertions(+), 74 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1efab9db0c7..2c95f5e77a53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,8 +16,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed -- SNP attestation reports now use TAV's C API with `ccf::pal::snp::AttestationReport` as an owning smart-pointer alias. Use `parse_attestation_report_unverified()` to decode reports and `ccf::pal::verify_snp_attestation_report_and_get()` to apply TAV verification and CCF's policy. TAV byte accessors borrow report storage; destroying or replacing the owner invalidates those views. The packed `ccf::pal::snp::Attestation` wire-layout type and its legacy accessors remain available but are deprecated. (#8083) -- `ccf::pal::snp::get_attestation_bytes()` in `ccf/pal/snp_ioctl.h` requests an unverified SNP report as owned bytes, without using the legacy report type. `AttestationInterface::get_raw()` and its ioctl implementation remain available but are deprecated. (#8083) +- SNP attestation reports now use TAV's C API with `ccf::pal::snp::AttestationReport` as an owning smart-pointer alias. Use `parse_attestation_report_unverified()` to decode reports and `ccf::pal::verify_snp_attestation_report_and_get()` to apply TAV verification and CCF's policy. TAV byte accessors borrow report storage; destroying or replacing the owner invalidates those views. The packed `ccf::pal::snp::Attestation` wire-layout type and its legacy accessors remain available but are deprecated. Azure cache and THIM endorsement requests now encode the reported TCB as 16 hexadecimal digits, preserving leading zeroes. (#8083) +- `ccf::pal::snp::request_attestation()` in `ccf/pal/snp_ioctl.h` returns an `AttestationResponse` by value after checking ioctl safety sentinels and report size. Its `report_bytes` member owns the unverified report bytes. The `report()` accessor decodes those bytes into a fresh owning `AttestationReport` without verifying authenticity. The legacy `get_attestation()` signature is unchanged. `AttestationInterface::get_raw()` and its ioctl implementation remain available but are deprecated. (#8083) - CBOR parsing now rejects composite (array or map) and tagged values used as map keys anywhere in the decoded document, including nested maps in optional COSE headers (#8297). ### Removed diff --git a/include/ccf/pal/attestation_sev_snp.h b/include/ccf/pal/attestation_sev_snp.h index dbc39ea73e5c..0186cf2de543 100644 --- a/include/ccf/pal/attestation_sev_snp.h +++ b/include/ccf/pal/attestation_sev_snp.h @@ -561,10 +561,9 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== quote.get(), &reported_tcb_data, &reported_tcb_size); const auto reported_tcb_raw = std::span{reported_tcb_data, reported_tcb_size}; - uint64_t reported_tcb_value = 0; - std::memcpy( - &reported_tcb_value, reported_tcb_raw.data(), sizeof(reported_tcb_value)); - auto reported_tcb = fmt::format("{:0x}", reported_tcb_value); + auto reported_tcb = fmt::format( + "{: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 = @@ -703,11 +702,11 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== { public: [[deprecated( - "Use get_attestation_bytes() from ccf/pal/snp_ioctl.h and " - "parse_attestation_report_unverified")]] [[nodiscard]] virtual const snp:: - Attestation& - get() const = 0; - [[deprecated("Use get_attestation_bytes() from ccf/pal/snp_ioctl.h")]] + "Use request_attestation().report() from ccf/pal/snp_ioctl.h for " + "unverified parsing")]] [[nodiscard]] virtual const snp::Attestation& + get() const = 0; + [[deprecated( + "Use request_attestation().report_bytes from ccf/pal/snp_ioctl.h")]] virtual std::vector get_raw() = 0; virtual ~AttestationInterface() = default; diff --git a/include/ccf/pal/snp_ioctl.h b/include/ccf/pal/snp_ioctl.h index d1fe21eea0d2..b035fea8cf64 100644 --- a/include/ccf/pal/snp_ioctl.h +++ b/include/ccf/pal/snp_ioctl.h @@ -6,18 +6,20 @@ namespace ccf::pal::snp { + using AttestationResponse = ioctl6::AttestationResponse; + static inline bool supports_sev_snp() { return ioctl6::supports_sev_snp(); } - // Request a fresh, unverified SNP report as owned bytes. - static std::vector get_attestation_bytes( + // Request a fresh response with an owned, unverified report byte array. + static AttestationResponse request_attestation( const PlatformAttestationReportData& report_data) { if (ioctl6::supports_sev_snp()) { - return ioctl6::get_attestation_bytes(report_data); + return ioctl6::request_attestation(report_data); } throw std::logic_error("SEV-SNP not supported"); } diff --git a/include/ccf/pal/snp_ioctl6.h b/include/ccf/pal/snp_ioctl6.h index 136d64d44ce4..e895e83c18a7 100644 --- a/include/ccf/pal/snp_ioctl6.h +++ b/include/ccf/pal/snp_ioctl6.h @@ -32,22 +32,30 @@ namespace ccf::pal::snp::ioctl6 // https://github.com/torvalds/linux/blob/v6.8/drivers/virt/coco/sev-guest/sev-guest.h // https://github.com/torvalds/linux/blob/v6.8/include/uapi/linux/sev-guest.h constexpr size_t ATTESTATION_RESPONSE_SIZE = 4000; - struct AttestationResponse - { - uint32_t status = 0; - uint32_t report_size = 0; - std::array reserved = {}; - std::array report = {}; - std::array< - uint8_t, - ATTESTATION_RESPONSE_SIZE - 0x20 - attestation_report_size> - padding = {}; - }; - static_assert(sizeof(AttestationResponse) == ATTESTATION_RESPONSE_SIZE); - static_assert(offsetof(AttestationResponse, report_size) == 0x04); - static_assert(offsetof(AttestationResponse, report) == 0x20); } + struct AttestationResponse + { + uint32_t status = 0; + uint32_t report_size = 0; + std::array reserved = {}; + std::array report_bytes = {}; + std::array< + uint8_t, + detail::ATTESTATION_RESPONSE_SIZE - 0x20 - attestation_report_size> + padding = {}; + + // Decodes owned bytes into a fresh report without verifying authenticity. + [[nodiscard]] snp::AttestationReport report() const + { + return snp::parse_attestation_report_unverified(report_bytes); + } + }; + static_assert( + sizeof(AttestationResponse) == detail::ATTESTATION_RESPONSE_SIZE); + static_assert(offsetof(AttestationResponse, report_size) == 0x04); + static_assert(offsetof(AttestationResponse, report_bytes) == 0x20); + #pragma pack(push, 1) // Helper to add padding to a struct, so that the resulting struct has some // minimum size. As a minor detail, the padding will be initialised to 0. @@ -142,7 +150,7 @@ namespace ccf::pal::snp::ioctl6 uint32_t status = 0; uint32_t report_size = 0; uint8_t reserved[0x20 - 0x8] = {0}; - [[deprecated("Use get_attestation_bytes()")]] + [[deprecated("Use request_attestation().report() for unverified parsing")]] snp::Attestation report = {}; uint8_t padding[64] = {0}; // padding to the size of SEV_SNP_REPORT_RSP_BUF_SZ (i.e., 1280 bytes) @@ -226,18 +234,22 @@ namespace ccf::pal::snp::ioctl6 namespace detail { - using GuestRequestAttestationBytes = + using AttestationRequest = GuestRequest; static_assert( - sizeof(GuestRequestAttestationBytes) == sizeof(GuestRequestAttestation)); + sizeof(AttestationRequest) == sizeof(GuestRequestAttestation)); + static_assert(offsetof(AttestationRequest, req_data) == 8); + static_assert(offsetof(AttestationRequest, resp_wrapper) == 16); + static_assert(offsetof(AttestationRequest, exit_info) == 24); + static_assert(sizeof(AttestationRequest) == 32); } // From linux/include/uapi/linux/sev-guest.h constexpr char SEV_GUEST_IOC_TYPE = 'S'; constexpr int SEV_SNP_GUEST_MSG_REPORT = - _IOWR(SEV_GUEST_IOC_TYPE, 0x0, detail::GuestRequestAttestationBytes); + _IOWR(SEV_GUEST_IOC_TYPE, 0x0, detail::AttestationRequest); static_assert( - _IOWR(SEV_GUEST_IOC_TYPE, 0x0, detail::GuestRequestAttestationBytes) == + _IOWR(SEV_GUEST_IOC_TYPE, 0x0, detail::AttestationRequest) == _IOWR(SEV_GUEST_IOC_TYPE, 0x0, GuestRequestAttestation)); constexpr int SEV_SNP_GUEST_MSG_DERIVED_KEY = _IOWR(SEV_GUEST_IOC_TYPE, 0x1, GuestRequestDerivedKey); @@ -260,13 +272,6 @@ namespace ccf::pal::snp::ioctl6 } } - inline std::vector extract_attestation_bytes( - const AttestationResponse& response) - { - validate_report_size(response.report_size); - return {response.report.begin(), response.report.end()}; - } - template void request_attestation( const PlatformAttestationReportData& report_data, @@ -321,12 +326,14 @@ namespace ccf::pal::snp::ioctl6 } } - static std::vector get_attestation_bytes( + // Validates ioctl safety and report size, not report authenticity. + static AttestationResponse request_attestation( const PlatformAttestationReportData& report_data) { - IoctlSentinel response; + IoctlSentinel response; detail::request_attestation(report_data, response); - return detail::extract_attestation_bytes(response.data); + detail::validate_report_size(response.data.report_size); + return response.data; } class Attestation : public AttestationInterface @@ -343,16 +350,15 @@ namespace ccf::pal::snp::ioctl6 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" [[deprecated( - "Use get_attestation_bytes() and " - "parse_attestation_report_unverified")]] [[nodiscard]] const ccf::pal:: - snp::Attestation& - get() const override + "Use request_attestation().report() for unverified " + "parsing")]] [[nodiscard]] const ccf::pal::snp::Attestation& + get() const override { detail::validate_report_size(padded_resp.report_size); return padded_resp.report; } - [[deprecated("Use get_attestation_bytes()")]] + [[deprecated("Use request_attestation().report_bytes")]] std::vector get_raw() override { detail::validate_report_size(padded_resp.report_size); diff --git a/src/pal/quote_generation.h b/src/pal/quote_generation.h index b11e5519d869..407f9487de7e 100644 --- a/src/pal/quote_generation.h +++ b/src/pal/quote_generation.h @@ -89,9 +89,10 @@ namespace ccf::pal { QuoteInfo node_quote_info = {}; node_quote_info.format = QuoteFormat::amd_sev_snp_v1; - node_quote_info.quote = snp::get_attestation_bytes(report_data); - auto report = - snp::parse_attestation_report_unverified(node_quote_info.quote); + auto attestation = snp::request_attestation(report_data); + node_quote_info.quote.assign( + attestation.report_bytes.begin(), attestation.report_bytes.end()); + auto report = attestation.report(); if ( tav_snp_attestation_report_version(report.get()) < diff --git a/src/pal/test/snp_attestation_validation.cpp b/src/pal/test/snp_attestation_validation.cpp index 8013203834b1..c0d76b9328ce 100644 --- a/src/pal/test/snp_attestation_validation.cpp +++ b/src/pal/test/snp_attestation_validation.cpp @@ -385,28 +385,42 @@ TEST_CASE("SNP verification rejects other quote formats before parsing") } } -TEST_CASE("SNP ioctl response bytes exclude response headers and padding") +TEST_CASE( + "SNP ioctl response owns report bytes separately from header and padding") { using namespace ccf::pal::snp; static_assert( std::is_same_v< - decltype(get_attestation_bytes( + decltype(request_attestation( std::declval())), - std::vector>); + AttestationResponse>); + static_assert( + std::is_same_v< + decltype(ioctl6::request_attestation( + std::declval())), + AttestationResponse>); - using Response = ioctl6::detail::AttestationResponse; + using Response = AttestationResponse; + static_assert(std::is_standard_layout_v); + static_assert(std::is_trivially_copyable_v); + static_assert(std::is_same_v< + decltype(std::declval().report()), + AttestationReport>); + static_assert(std::is_same_v< + decltype(Response::report_bytes), + std::array>); static_assert(sizeof(Response) == 4000); static_assert(offsetof(Response, status) == 0); static_assert(offsetof(Response, report_size) == 4); static_assert(offsetof(Response, reserved) == 8); - static_assert(offsetof(Response, report) == 0x20); + static_assert(offsetof(Response, report_bytes) == 0x20); static_assert(offsetof(Response, padding) == 0x20 + attestation_report_size); ioctl6::IoctlSentinel response; CHECK(response.data.status == 0); CHECK(response.data.report_size == 0); CHECK(response.data.reserved == decltype(response.data.reserved){}); - CHECK(response.data.report == decltype(response.data.report){}); + CHECK(response.data.report_bytes == decltype(response.data.report_bytes){}); CHECK(response.data.padding == decltype(response.data.padding){}); response.data.status = 0xa5a5a5a5; response.data.report_size = attestation_report_size; @@ -415,44 +429,71 @@ TEST_CASE("SNP ioctl response bytes exclude response headers and padding") std::copy( testing::milan_attestation.begin(), testing::milan_attestation.end(), - response.data.report.begin()); + response.data.report_bytes.begin()); - const auto report_bytes = - ioctl6::detail::extract_attestation_bytes(response.data); - CHECK(report_bytes == testing::milan_attestation); + ioctl6::detail::validate_report_size(response.data.report_size); + auto attestation = response.data; + CHECK(std::equal( + attestation.report_bytes.begin(), + attestation.report_bytes.end(), + testing::milan_attestation.begin(), + testing::milan_attestation.end())); + auto report = attestation.report(); + CHECK(tav_snp_attestation_report_version(report.get()) == 3); CHECK(response.sentinels_intact()); response.data = {}; - CHECK(report_bytes == testing::milan_attestation); + CHECK(std::equal( + attestation.report_bytes.begin(), + attestation.report_bytes.end(), + testing::milan_attestation.begin(), + testing::milan_attestation.end())); - response.post_sentinels[1] ^= 1; - CHECK_FALSE(response.sentinels_intact()); + attestation.report_bytes[0x050] ^= 1; + const auto updated_report = attestation.report(); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_report_data(updated_report.get(), &data, &size); + REQUIRE(size == ccf::pal::snp_attestation_report_data_size); + CHECK(data[0] == attestation.report_bytes[0x050]); + tav_snp_attestation_report_report_data(report.get(), &data, &size); + REQUIRE(size == ccf::pal::snp_attestation_report_data_size); + CHECK(data[0] == testing::milan_attestation[0x050]); + + for (auto* sentinels : {response.pre_sentinels, response.post_sentinels}) + { + for (size_t i = 0; i < response.num_sentinel_bytes; ++i) + { + sentinels[i] ^= 1; + CHECK_FALSE(response.sentinels_intact()); + sentinels[i] ^= 1; + CHECK(response.sentinels_intact()); + } + } } TEST_CASE("SNP ioctl response bytes reject invalid report sizes") { using namespace ccf::pal::snp; - ioctl6::detail::AttestationResponse response = {}; for (const uint32_t report_size : {0U, 1183U, 1185U, std::numeric_limits::max()}) { - response.report_size = report_size; const auto expected_error = fmt::format( "Unexpected SEV-SNP attestation report size: {} != {}", report_size, attestation_report_size); CHECK_THROWS_WITH_AS( - ioctl6::detail::extract_attestation_bytes(response), + ioctl6::detail::validate_report_size(report_size), expected_error.c_str(), std::logic_error); } } -TEST_CASE("SNP byte acquisition rejects oversized report data before ioctl") +TEST_CASE("SNP request rejects oversized report data before ioctl") { ccf::pal::PlatformAttestationReportData report_data; report_data.data.resize(ccf::pal::snp_attestation_report_data_size + 1); CHECK_THROWS_WITH_AS( - ccf::pal::snp::ioctl6::get_attestation_bytes(report_data), + ccf::pal::snp::ioctl6::request_attestation(report_data), "User-defined report data is larger than available space", std::logic_error); } @@ -914,6 +955,38 @@ TEST_CASE("Quote endorsements url generation") } } +TEST_CASE("Quote endorsement TCB formatting preserves leading zeroes") +{ + using namespace ccf::pal::snp; + + for (const auto& expected_tcb : + {"0000000000000000", "0001000000000004", "0b18000000000004"}) + { + auto report = testing::milan_attestation; + const auto tcb_bytes = ccf::ds::from_hex(expected_tcb); + std::reverse_copy( + tcb_bytes.begin(), tcb_bytes.end(), report.begin() + 0x180); + auto quote = parse_attestation_report_unverified(report); + + const auto default_config = make_endorsement_endpoint_configuration(quote); + REQUIRE_EQ(default_config.servers.size(), 1); + REQUIRE_EQ(default_config.servers.front().size(), 1); + CHECK(default_config.servers.front().front().uri.ends_with( + std::string("/") + expected_tcb)); + + const auto config = make_endorsement_endpoint_configuration( + quote, + {{EndorsementsEndpointType::Azure}, {EndorsementsEndpointType::THIM}}); + REQUIRE_EQ(config.servers.size(), 2); + REQUIRE_EQ(config.servers.front().size(), 1); + REQUIRE_EQ(config.servers.back().size(), 1); + CHECK(config.servers.front().front().uri.ends_with( + std::string("/") + expected_tcb)); + CHECK_EQ( + config.servers.back().front().params.at("tcbVersion"), expected_tcb); + } +} + TEST_CASE("Quote endorsements generation for v2 attestation version fails") { auto v2_format_milan_attestation = diff --git a/src/pal/test/snp_ioctl_test.cpp b/src/pal/test/snp_ioctl_test.cpp index 407b41b88a69..3c03ce4b1d92 100644 --- a/src/pal/test/snp_ioctl_test.cpp +++ b/src/pal/test/snp_ioctl_test.cpp @@ -23,14 +23,13 @@ TEST_CASE("SNP request attestation") snp_report_data.report_data.begin(), snp_report_data.report_data.end(), 0); PlatformAttestationReportData report_data(snp_report_data); - const auto report_bytes = snp::get_attestation_bytes(report_data); - REQUIRE(report_bytes.size() == snp::attestation_report_size); - const auto attestation = - snp::parse_attestation_report_unverified(report_bytes); + const auto attestation = snp::request_attestation(report_data); + REQUIRE(attestation.report_size == snp::attestation_report_size); + const auto report = attestation.report(); const uint8_t* data = nullptr; size_t size = 0; - tav_snp_attestation_report_report_data(attestation.get(), &data, &size); + tav_snp_attestation_report_report_data(report.get(), &data, &size); SnpAttestationReportData attested_report_data({data, size}); REQUIRE_EQ(snp_report_data.report_data, attested_report_data.report_data); From b623586837e7325f7c7f503bdda1512010fd8710 Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Thu, 10 Sep 2026 14:23:18 +0100 Subject: [PATCH 09/14] chkpt --- CHANGELOG.md | 2 +- include/ccf/pal/attestation_sev_snp.h | 7 +- include/ccf/pal/snp_ioctl6.h | 50 +- src/js/extensions/snp_attestation.cpp | 560 ++++++++++---------- src/pal/quote_generation.h | 3 +- src/pal/test/snp_attestation_validation.cpp | 60 +-- src/pal/test/snp_ioctl_test.cpp | 3 +- 7 files changed, 336 insertions(+), 349 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c95f5e77a53..7165aa755e84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed - SNP attestation reports now use TAV's C API with `ccf::pal::snp::AttestationReport` as an owning smart-pointer alias. Use `parse_attestation_report_unverified()` to decode reports and `ccf::pal::verify_snp_attestation_report_and_get()` to apply TAV verification and CCF's policy. TAV byte accessors borrow report storage; destroying or replacing the owner invalidates those views. The packed `ccf::pal::snp::Attestation` wire-layout type and its legacy accessors remain available but are deprecated. Azure cache and THIM endorsement requests now encode the reported TCB as 16 hexadecimal digits, preserving leading zeroes. (#8083) -- `ccf::pal::snp::request_attestation()` in `ccf/pal/snp_ioctl.h` returns an `AttestationResponse` by value after checking ioctl safety sentinels and report size. Its `report_bytes` member owns the unverified report bytes. The `report()` accessor decodes those bytes into a fresh owning `AttestationReport` without verifying authenticity. The legacy `get_attestation()` signature is unchanged. `AttestationInterface::get_raw()` and its ioctl implementation remain available but are deprecated. (#8083) +- `ccf::pal::snp::request_attestation()` in `ccf/pal/snp_ioctl.h` returns an `AttestationResponse` by value after checking ioctl safety sentinels and report size. Its `report_bytes` member owns the unverified report bytes. Decode them explicitly with `parse_attestation_report_unverified(attestation.report_bytes)`. The legacy `get_attestation()` signature is unchanged. `AttestationInterface::get_raw()` and its ioctl implementation remain available but are deprecated. (#8083) - CBOR parsing now rejects composite (array or map) and tagged values used as map keys anywhere in the decoded document, including nested maps in optional COSE headers (#8297). ### Removed diff --git a/include/ccf/pal/attestation_sev_snp.h b/include/ccf/pal/attestation_sev_snp.h index 0186cf2de543..6430b89e71b8 100644 --- a/include/ccf/pal/attestation_sev_snp.h +++ b/include/ccf/pal/attestation_sev_snp.h @@ -702,9 +702,10 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== { public: [[deprecated( - "Use request_attestation().report() from ccf/pal/snp_ioctl.h for " - "unverified parsing")]] [[nodiscard]] virtual const snp::Attestation& - get() const = 0; + "Use request_attestation().report_bytes from ccf/pal/snp_ioctl.h and " + "parse_attestation_report_unverified")]] [[nodiscard]] virtual const snp:: + Attestation& + get() const = 0; [[deprecated( "Use request_attestation().report_bytes from ccf/pal/snp_ioctl.h")]] virtual std::vector get_raw() = 0; diff --git a/include/ccf/pal/snp_ioctl6.h b/include/ccf/pal/snp_ioctl6.h index e895e83c18a7..e5ea88d8eec0 100644 --- a/include/ccf/pal/snp_ioctl6.h +++ b/include/ccf/pal/snp_ioctl6.h @@ -44,12 +44,6 @@ namespace ccf::pal::snp::ioctl6 uint8_t, detail::ATTESTATION_RESPONSE_SIZE - 0x20 - attestation_report_size> padding = {}; - - // Decodes owned bytes into a fresh report without verifying authenticity. - [[nodiscard]] snp::AttestationReport report() const - { - return snp::parse_attestation_report_unverified(report_bytes); - } }; static_assert( sizeof(AttestationResponse) == detail::ATTESTATION_RESPONSE_SIZE); @@ -150,7 +144,7 @@ namespace ccf::pal::snp::ioctl6 uint32_t status = 0; uint32_t report_size = 0; uint8_t reserved[0x20 - 0x8] = {0}; - [[deprecated("Use request_attestation().report() for unverified parsing")]] + [[deprecated("Use request_attestation().report_bytes")]] snp::Attestation report = {}; uint8_t padding[64] = {0}; // padding to the size of SEV_SNP_REPORT_RSP_BUF_SZ (i.e., 1280 bytes) @@ -261,17 +255,6 @@ namespace ccf::pal::snp::ioctl6 namespace detail { - inline void validate_report_size(uint32_t report_size) - { - if (report_size != attestation_report_size) - { - throw std::logic_error(fmt::format( - "Unexpected SEV-SNP attestation report size: {} != {}", - report_size, - attestation_report_size)); - } - } - template void request_attestation( const PlatformAttestationReportData& report_data, @@ -332,7 +315,13 @@ namespace ccf::pal::snp::ioctl6 { IoctlSentinel response; detail::request_attestation(report_data, response); - detail::validate_report_size(response.data.report_size); + if (response.data.report_size != attestation_report_size) + { + throw std::logic_error(fmt::format( + "Unexpected SEV-SNP attestation report size: {} != {}", + response.data.report_size, + attestation_report_size)); + } return response.data; } @@ -350,18 +339,31 @@ namespace ccf::pal::snp::ioctl6 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" [[deprecated( - "Use request_attestation().report() for unverified " - "parsing")]] [[nodiscard]] const ccf::pal::snp::Attestation& - get() const override + "Use request_attestation().report_bytes and " + "parse_attestation_report_unverified")]] [[nodiscard]] const ccf::pal:: + snp::Attestation& + get() const override { - detail::validate_report_size(padded_resp.report_size); + if (padded_resp.report_size != attestation_report_size) + { + throw std::logic_error(fmt::format( + "Unexpected SEV-SNP attestation report size: {} != {}", + padded_resp.report_size, + attestation_report_size)); + } return padded_resp.report; } [[deprecated("Use request_attestation().report_bytes")]] std::vector get_raw() override { - detail::validate_report_size(padded_resp.report_size); + if (padded_resp.report_size != attestation_report_size) + { + throw std::logic_error(fmt::format( + "Unexpected SEV-SNP attestation report size: {} != {}", + padded_resp.report_size, + attestation_report_size)); + } const auto* report = reinterpret_cast(&padded_resp.report); return {report, report + attestation_report_size}; diff --git a/src/js/extensions/snp_attestation.cpp b/src/js/extensions/snp_attestation.cpp index 9e8903e472f1..e8bbaceaed3e 100644 --- a/src/js/extensions/snp_attestation.cpp +++ b/src/js/extensions/snp_attestation.cpp @@ -100,12 +100,10 @@ namespace ccf::js::extensions pal::PlatformAttestationMeasurement measurement = {}; pal::PlatformAttestationReportData report_data = {}; std::optional parsed_uvm_endorsements; - std::optional verified_attestation; - try { - verified_attestation.emplace(pal::verify_snp_attestation_report_and_get( - quote_info, measurement, report_data)); + const auto attestation = pal::verify_snp_attestation_report_and_get( + quote_info, measurement, report_data); if (uvm_endorsements.has_value()) { parsed_uvm_endorsements = @@ -114,310 +112,316 @@ namespace ccf::js::extensions measurement, default_uvm_roots_of_trust); } - } - catch (const std::exception& e) - { - return JS_ThrowRangeError(ctx, "%s", e.what()); - } - - const auto& attestation = verified_attestation.value(); - - auto r = jsctx.new_obj(); - JS_CHECK_EXC(r); - - auto a = jsctx.new_obj(); - JS_CHECK_EXC(a); - - JS_CHECK_SET(a.set_uint32( - "version", tav_snp_attestation_report_version(attestation.get()))); - JS_CHECK_SET(a.set_uint32( - "guest_svn", tav_snp_attestation_report_guest_svn(attestation.get()))); - - auto policy = jsctx.new_obj(); - JS_CHECK_EXC(policy); - - JS_CHECK_SET(policy.set_uint32( - "abi_minor", - tav_snp_attestation_report_policy_abi_minor(attestation.get()))); - JS_CHECK_SET(policy.set_uint32( - "abi_major", - tav_snp_attestation_report_policy_abi_major(attestation.get()))); - JS_CHECK_SET(policy.set_uint32( - "smt", tav_snp_attestation_report_policy_smt(attestation.get()))); - JS_CHECK_SET(policy.set_uint32( - "migrate_ma", - tav_snp_attestation_report_policy_migrate_ma(attestation.get()))); - JS_CHECK_SET(policy.set_uint32( - "debug", tav_snp_attestation_report_policy_debug(attestation.get()))); - JS_CHECK_SET(policy.set_uint32( - "single_socket", - tav_snp_attestation_report_policy_single_socket(attestation.get()))); - - JS_CHECK_SET(a.set("policy", std::move(policy))); - - { - const uint8_t* data = nullptr; - size_t size = 0; - tav_snp_attestation_report_family_id(attestation.get(), &data, &size); - auto family_id = - jsctx.new_array_buffer_copy(std::span{data, size}); - JS_CHECK_EXC(family_id); - JS_CHECK_SET(a.set("family_id", std::move(family_id))); - } - - { - const uint8_t* data = nullptr; - size_t size = 0; - tav_snp_attestation_report_image_id(attestation.get(), &data, &size); - auto image_id = - jsctx.new_array_buffer_copy(std::span{data, size}); - JS_CHECK_EXC(image_id); - JS_CHECK_SET(a.set("image_id", std::move(image_id))); - } - - JS_CHECK_SET(a.set_uint32( - "vmpl", tav_snp_attestation_report_vmpl(attestation.get()))); - JS_CHECK_SET(a.set_uint32( - "signature_algo", - static_cast( - tav_snp_attestation_report_signature_algo(attestation.get())))); - - { - const uint8_t* data = nullptr; - size_t size = 0; - tav_snp_attestation_report_platform_version( - attestation.get(), &data, &size); - auto platform_version = jsctx.wrap( - make_js_tcb_version(jsctx, std::span{data, size})); - JS_CHECK_EXC(platform_version); - JS_CHECK_SET(a.set("platform_version", std::move(platform_version))); - } + auto r = jsctx.new_obj(); + JS_CHECK_EXC(r); + + auto a = jsctx.new_obj(); + JS_CHECK_EXC(a); + + JS_CHECK_SET(a.set_uint32( + "version", tav_snp_attestation_report_version(attestation.get()))); + JS_CHECK_SET(a.set_uint32( + "guest_svn", + tav_snp_attestation_report_guest_svn(attestation.get()))); + + auto policy = jsctx.new_obj(); + JS_CHECK_EXC(policy); + + JS_CHECK_SET(policy.set_uint32( + "abi_minor", + tav_snp_attestation_report_policy_abi_minor(attestation.get()))); + JS_CHECK_SET(policy.set_uint32( + "abi_major", + tav_snp_attestation_report_policy_abi_major(attestation.get()))); + JS_CHECK_SET(policy.set_uint32( + "smt", tav_snp_attestation_report_policy_smt(attestation.get()))); + JS_CHECK_SET(policy.set_uint32( + "migrate_ma", + tav_snp_attestation_report_policy_migrate_ma(attestation.get()))); + JS_CHECK_SET(policy.set_uint32( + "debug", tav_snp_attestation_report_policy_debug(attestation.get()))); + JS_CHECK_SET(policy.set_uint32( + "single_socket", + tav_snp_attestation_report_policy_single_socket(attestation.get()))); + + JS_CHECK_SET(a.set("policy", std::move(policy))); - { - auto platform_info = jsctx.new_obj(); - JS_CHECK_EXC(platform_info); - const auto raw_platform_info = - tav_snp_attestation_report_platform_info(attestation.get()); - JS_CHECK_SET(platform_info.set_uint32("smt_en", raw_platform_info & 1)); - JS_CHECK_SET( - platform_info.set_uint32("tsme_en", (raw_platform_info >> 1) & 1)); - JS_CHECK_SET(a.set("plaform_info", std::move(platform_info))); - } + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_family_id(attestation.get(), &data, &size); + auto family_id = + jsctx.new_array_buffer_copy(std::span{data, size}); + JS_CHECK_EXC(family_id); + JS_CHECK_SET(a.set("family_id", std::move(family_id))); + } - { - auto flags = jsctx.new_obj(); - JS_CHECK_EXC(flags); - JS_CHECK_SET(flags.set_uint32( - "author_key_en", - tav_snp_attestation_report_flags_author_key_en(attestation.get()))); - JS_CHECK_SET(flags.set_uint32( - "mask_chip_key", - tav_snp_attestation_report_flags_mask_chip_key(attestation.get()))); - JS_CHECK_SET(flags.set_uint32( - "signing_key", - tav_snp_attestation_report_flags_signing_key(attestation.get()))); - JS_CHECK_SET(a.set("flags", std::move(flags))); - } + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_image_id(attestation.get(), &data, &size); + auto image_id = + jsctx.new_array_buffer_copy(std::span{data, size}); + JS_CHECK_EXC(image_id); + JS_CHECK_SET(a.set("image_id", std::move(image_id))); + } - { - const uint8_t* data = nullptr; - size_t size = 0; - tav_snp_attestation_report_report_data(attestation.get(), &data, &size); - auto attestation_report_data = - jsctx.new_array_buffer_copy(std::span{data, size}); - JS_CHECK_EXC(attestation_report_data); - JS_CHECK_SET(a.set("report_data", std::move(attestation_report_data))); - } + JS_CHECK_SET(a.set_uint32( + "vmpl", tav_snp_attestation_report_vmpl(attestation.get()))); + JS_CHECK_SET(a.set_uint32( + "signature_algo", + static_cast( + tav_snp_attestation_report_signature_algo(attestation.get())))); - { - const uint8_t* data = nullptr; - size_t size = 0; - tav_snp_attestation_report_measurement(attestation.get(), &data, &size); - auto attestation_measurement = - jsctx.new_array_buffer_copy(std::span{data, size}); - JS_CHECK_EXC(attestation_measurement); - JS_CHECK_SET(a.set("measurement", std::move(attestation_measurement))); - } + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_platform_version( + attestation.get(), &data, &size); + auto platform_version = jsctx.wrap( + make_js_tcb_version(jsctx, std::span{data, size})); + JS_CHECK_EXC(platform_version); + JS_CHECK_SET(a.set("platform_version", std::move(platform_version))); + } - { - const uint8_t* data = nullptr; - size_t size = 0; - tav_snp_attestation_report_host_data(attestation.get(), &data, &size); - auto attestation_host_data = - jsctx.new_array_buffer_copy(std::span{data, size}); - JS_CHECK_EXC(attestation_host_data); - JS_CHECK_SET(a.set("host_data", std::move(attestation_host_data))); - } + { + auto platform_info = jsctx.new_obj(); + JS_CHECK_EXC(platform_info); + const auto raw_platform_info = + tav_snp_attestation_report_platform_info(attestation.get()); + JS_CHECK_SET( + platform_info.set_uint32("smt_en", raw_platform_info & 1)); + JS_CHECK_SET( + platform_info.set_uint32("tsme_en", (raw_platform_info >> 1) & 1)); + JS_CHECK_SET(a.set("plaform_info", std::move(platform_info))); + } - { - const uint8_t* data = nullptr; - size_t size = 0; - tav_snp_attestation_report_id_key_digest( - attestation.get(), &data, &size); - auto attestation_id_key_digest = - jsctx.new_array_buffer_copy(std::span{data, size}); - JS_CHECK_EXC(attestation_id_key_digest); - JS_CHECK_SET( - a.set("id_key_digest", std::move(attestation_id_key_digest))); - } + { + auto flags = jsctx.new_obj(); + JS_CHECK_EXC(flags); + JS_CHECK_SET(flags.set_uint32( + "author_key_en", + tav_snp_attestation_report_flags_author_key_en(attestation.get()))); + JS_CHECK_SET(flags.set_uint32( + "mask_chip_key", + tav_snp_attestation_report_flags_mask_chip_key(attestation.get()))); + JS_CHECK_SET(flags.set_uint32( + "signing_key", + tav_snp_attestation_report_flags_signing_key(attestation.get()))); + JS_CHECK_SET(a.set("flags", std::move(flags))); + } - { - const uint8_t* data = nullptr; - size_t size = 0; - tav_snp_attestation_report_author_key_digest( - attestation.get(), &data, &size); - auto attestation_author_key_digest = - jsctx.new_array_buffer_copy(std::span{data, size}); - JS_CHECK_EXC(attestation_author_key_digest); - JS_CHECK_SET( - a.set("author_key_digest", std::move(attestation_author_key_digest))); - } + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_report_data( + attestation.get(), &data, &size); + auto attestation_report_data = + jsctx.new_array_buffer_copy(std::span{data, size}); + JS_CHECK_EXC(attestation_report_data); + JS_CHECK_SET( + a.set("report_data", std::move(attestation_report_data))); + } - { - const uint8_t* data = nullptr; - size_t size = 0; - tav_snp_attestation_report_report_id(attestation.get(), &data, &size); - auto attestation_report_id = - jsctx.new_array_buffer_copy(std::span{data, size}); - JS_CHECK_EXC(attestation_report_id); - JS_CHECK_SET(a.set("report_id", std::move(attestation_report_id))); - } + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_measurement( + attestation.get(), &data, &size); + auto attestation_measurement = + jsctx.new_array_buffer_copy(std::span{data, size}); + JS_CHECK_EXC(attestation_measurement); + JS_CHECK_SET( + a.set("measurement", std::move(attestation_measurement))); + } - { - const uint8_t* data = nullptr; - size_t size = 0; - tav_snp_attestation_report_report_id_ma( - attestation.get(), &data, &size); - auto attestation_report_id_ma = - jsctx.new_array_buffer_copy(std::span{data, size}); - JS_CHECK_EXC(attestation_report_id_ma); - JS_CHECK_SET( - a.set("report_id_ma", std::move(attestation_report_id_ma))); - } + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_host_data(attestation.get(), &data, &size); + auto attestation_host_data = + jsctx.new_array_buffer_copy(std::span{data, size}); + JS_CHECK_EXC(attestation_host_data); + JS_CHECK_SET(a.set("host_data", std::move(attestation_host_data))); + } - { - const uint8_t* data = nullptr; - size_t size = 0; - tav_snp_attestation_report_reported_tcb( - attestation.get(), &data, &size); - auto reported_tcb = jsctx.wrap( - make_js_tcb_version(jsctx, std::span{data, size})); - JS_CHECK_EXC(reported_tcb); - JS_CHECK_SET(a.set("reported_tcb", std::move(reported_tcb))); - } + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_id_key_digest( + attestation.get(), &data, &size); + auto attestation_id_key_digest = + jsctx.new_array_buffer_copy(std::span{data, size}); + JS_CHECK_EXC(attestation_id_key_digest); + JS_CHECK_SET( + a.set("id_key_digest", std::move(attestation_id_key_digest))); + } - JS_CHECK_SET(a.set_uint32( - "cpuid_fam_id", - tav_snp_attestation_report_cpuid_fam_id(attestation.get()))); - JS_CHECK_SET(a.set_uint32( - "cpuid_mod_id", - tav_snp_attestation_report_cpuid_mod_id(attestation.get()))); - JS_CHECK_SET(a.set_uint32( - "cpuid_step", - tav_snp_attestation_report_cpuid_step(attestation.get()))); + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_author_key_digest( + attestation.get(), &data, &size); + auto attestation_author_key_digest = + jsctx.new_array_buffer_copy(std::span{data, size}); + JS_CHECK_EXC(attestation_author_key_digest); + JS_CHECK_SET(a.set( + "author_key_digest", std::move(attestation_author_key_digest))); + } - { - const uint8_t* data = nullptr; - size_t size = 0; - tav_snp_attestation_report_chip_id(attestation.get(), &data, &size); - auto attestation_chip_id = - jsctx.new_array_buffer_copy(std::span{data, size}); - JS_CHECK_EXC(attestation_chip_id); - JS_CHECK_SET(a.set("chip_id", std::move(attestation_chip_id))); - } + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_report_id(attestation.get(), &data, &size); + auto attestation_report_id = + jsctx.new_array_buffer_copy(std::span{data, size}); + JS_CHECK_EXC(attestation_report_id); + JS_CHECK_SET(a.set("report_id", std::move(attestation_report_id))); + } - { - const uint8_t* data = nullptr; - size_t size = 0; - tav_snp_attestation_report_committed_tcb( - attestation.get(), &data, &size); - auto committed_tcb = jsctx.wrap( - make_js_tcb_version(jsctx, std::span{data, size})); - JS_CHECK_EXC(committed_tcb); - JS_CHECK_SET(a.set("committed_tcb", std::move(committed_tcb))); - } + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_report_id_ma( + attestation.get(), &data, &size); + auto attestation_report_id_ma = + jsctx.new_array_buffer_copy(std::span{data, size}); + JS_CHECK_EXC(attestation_report_id_ma); + JS_CHECK_SET( + a.set("report_id_ma", std::move(attestation_report_id_ma))); + } - JS_CHECK_SET(a.set_uint32( - "current_minor", - tav_snp_attestation_report_current_minor(attestation.get()))); - JS_CHECK_SET(a.set_uint32( - "current_build", - tav_snp_attestation_report_current_build(attestation.get()))); - JS_CHECK_SET(a.set_uint32( - "current_major", - tav_snp_attestation_report_current_major(attestation.get()))); - JS_CHECK_SET(a.set_uint32( - "committed_build", - tav_snp_attestation_report_committed_build(attestation.get()))); - JS_CHECK_SET(a.set_uint32( - "committed_minor", - tav_snp_attestation_report_committed_minor(attestation.get()))); - JS_CHECK_SET(a.set_uint32( - "committed_major", - tav_snp_attestation_report_committed_major(attestation.get()))); + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_reported_tcb( + attestation.get(), &data, &size); + auto reported_tcb = jsctx.wrap( + make_js_tcb_version(jsctx, std::span{data, size})); + JS_CHECK_EXC(reported_tcb); + JS_CHECK_SET(a.set("reported_tcb", std::move(reported_tcb))); + } - { - const uint8_t* data = nullptr; - size_t size = 0; - tav_snp_attestation_report_launch_tcb(attestation.get(), &data, &size); - auto launch_tcb = jsctx.wrap( - make_js_tcb_version(jsctx, std::span{data, size})); - JS_CHECK_EXC(launch_tcb); - JS_CHECK_SET(a.set("launch_tcb", std::move(launch_tcb))); - } + JS_CHECK_SET(a.set_uint32( + "cpuid_fam_id", + tav_snp_attestation_report_cpuid_fam_id(attestation.get()))); + JS_CHECK_SET(a.set_uint32( + "cpuid_mod_id", + tav_snp_attestation_report_cpuid_mod_id(attestation.get()))); + JS_CHECK_SET(a.set_uint32( + "cpuid_step", + tav_snp_attestation_report_cpuid_step(attestation.get()))); - auto signature = jsctx.new_obj(); - JS_CHECK_EXC(signature); + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_chip_id(attestation.get(), &data, &size); + auto attestation_chip_id = + jsctx.new_array_buffer_copy(std::span{data, size}); + JS_CHECK_EXC(attestation_chip_id); + JS_CHECK_SET(a.set("chip_id", std::move(attestation_chip_id))); + } - { - const uint8_t* data = nullptr; - size_t size = 0; - tav_snp_attestation_report_signature_r(attestation.get(), &data, &size); - auto signature_r = - jsctx.new_array_buffer_copy(std::span{data, size}); - JS_CHECK_EXC(signature_r); - JS_CHECK_SET(signature.set("r", std::move(signature_r))); - } + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_committed_tcb( + attestation.get(), &data, &size); + auto committed_tcb = jsctx.wrap( + make_js_tcb_version(jsctx, std::span{data, size})); + JS_CHECK_EXC(committed_tcb); + JS_CHECK_SET(a.set("committed_tcb", std::move(committed_tcb))); + } - { - const uint8_t* data = nullptr; - size_t size = 0; - tav_snp_attestation_report_signature_s(attestation.get(), &data, &size); - auto signature_s = - jsctx.new_array_buffer_copy(std::span{data, size}); - JS_CHECK_EXC(signature_s); - JS_CHECK_SET(signature.set("s", std::move(signature_s))); - } + JS_CHECK_SET(a.set_uint32( + "current_minor", + tav_snp_attestation_report_current_minor(attestation.get()))); + JS_CHECK_SET(a.set_uint32( + "current_build", + tav_snp_attestation_report_current_build(attestation.get()))); + JS_CHECK_SET(a.set_uint32( + "current_major", + tav_snp_attestation_report_current_major(attestation.get()))); + JS_CHECK_SET(a.set_uint32( + "committed_build", + tav_snp_attestation_report_committed_build(attestation.get()))); + JS_CHECK_SET(a.set_uint32( + "committed_minor", + tav_snp_attestation_report_committed_minor(attestation.get()))); + JS_CHECK_SET(a.set_uint32( + "committed_major", + tav_snp_attestation_report_committed_major(attestation.get()))); - JS_CHECK_SET(a.set("signature", std::move(signature))); - JS_CHECK_SET(r.set("attestation", std::move(a))); + { + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_launch_tcb( + attestation.get(), &data, &size); + auto launch_tcb = jsctx.wrap( + make_js_tcb_version(jsctx, std::span{data, size})); + JS_CHECK_EXC(launch_tcb); + JS_CHECK_SET(a.set("launch_tcb", std::move(launch_tcb))); + } - if (parsed_uvm_endorsements.has_value()) - { - auto u = jsctx.new_obj(); - JS_CHECK_EXC(u); + auto signature = jsctx.new_obj(); + JS_CHECK_EXC(signature); { - auto did = jsctx.new_string(parsed_uvm_endorsements.value().did); - JS_CHECK_EXC(did); - JS_CHECK_SET(u.set("did", std::move(did))); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_signature_r( + attestation.get(), &data, &size); + auto signature_r = + jsctx.new_array_buffer_copy(std::span{data, size}); + JS_CHECK_EXC(signature_r); + JS_CHECK_SET(signature.set("r", std::move(signature_r))); } { - auto feed = jsctx.new_string(parsed_uvm_endorsements.value().feed); - JS_CHECK_EXC(feed); - JS_CHECK_SET(u.set("feed", std::move(feed))); + const uint8_t* data = nullptr; + size_t size = 0; + tav_snp_attestation_report_signature_s( + attestation.get(), &data, &size); + auto signature_s = + jsctx.new_array_buffer_copy(std::span{data, size}); + JS_CHECK_EXC(signature_s); + JS_CHECK_SET(signature.set("s", std::move(signature_s))); } + JS_CHECK_SET(a.set("signature", std::move(signature))); + JS_CHECK_SET(r.set("attestation", std::move(a))); + + if (parsed_uvm_endorsements.has_value()) { - auto svn = jsctx.new_string(parsed_uvm_endorsements.value().svn); - JS_CHECK_EXC(svn); - JS_CHECK_SET(u.set("svn", std::move(svn))); - JS_CHECK_SET(r.set("uvm_endorsements", std::move(u))); + auto u = jsctx.new_obj(); + JS_CHECK_EXC(u); + + { + auto did = jsctx.new_string(parsed_uvm_endorsements.value().did); + JS_CHECK_EXC(did); + JS_CHECK_SET(u.set("did", std::move(did))); + } + + { + auto feed = jsctx.new_string(parsed_uvm_endorsements.value().feed); + JS_CHECK_EXC(feed); + JS_CHECK_SET(u.set("feed", std::move(feed))); + } + + { + auto svn = jsctx.new_string(parsed_uvm_endorsements.value().svn); + JS_CHECK_EXC(svn); + JS_CHECK_SET(u.set("svn", std::move(svn))); + JS_CHECK_SET(r.set("uvm_endorsements", std::move(u))); + } } - } - return r.take(); + return r.take(); + } + catch (const std::exception& e) + { + return JS_ThrowRangeError(ctx, "%s", e.what()); + } } #pragma clang diagnostic pop diff --git a/src/pal/quote_generation.h b/src/pal/quote_generation.h index 407f9487de7e..3f5635b865e9 100644 --- a/src/pal/quote_generation.h +++ b/src/pal/quote_generation.h @@ -92,7 +92,8 @@ namespace ccf::pal auto attestation = snp::request_attestation(report_data); node_quote_info.quote.assign( attestation.report_bytes.begin(), attestation.report_bytes.end()); - auto report = attestation.report(); + auto report = + snp::parse_attestation_report_unverified(attestation.report_bytes); if ( tav_snp_attestation_report_version(report.get()) < diff --git a/src/pal/test/snp_attestation_validation.cpp b/src/pal/test/snp_attestation_validation.cpp index c0d76b9328ce..f71509e5cec2 100644 --- a/src/pal/test/snp_attestation_validation.cpp +++ b/src/pal/test/snp_attestation_validation.cpp @@ -21,7 +21,6 @@ #include #include -#include #include #include @@ -208,12 +207,21 @@ TEST_CASE("CCF policy is separate from generic TAV verification") TEST_CASE("unverified SNP report rejects invalid sizes") { - CHECK_THROWS_WITH_AS( - static_cast(ccf::pal::snp::parse_attestation_report_unverified( - std::vector(100))), - "SEV-SNP: TAV unverified report parsing failed (1): Invalid " - "attestation report: expected 1184 bytes, got 100", - std::logic_error); + for (const size_t size : {0U, 100U, 1183U, 1185U}) + { + const auto expected_error = size == 0 ? + "SEV-SNP: TAV unverified report parsing failed (1): attestation report " + "is empty" : + fmt::format( + "SEV-SNP: TAV unverified report parsing failed (1): Invalid " + "attestation report: expected 1184 bytes, got {}", + size); + CHECK_THROWS_WITH_AS( + static_cast(ccf::pal::snp::parse_attestation_report_unverified( + std::vector(size))), + expected_error.c_str(), + std::logic_error); + } } TEST_CASE("SNP byte accessors borrow report storage") @@ -403,9 +411,7 @@ TEST_CASE( using Response = AttestationResponse; static_assert(std::is_standard_layout_v); static_assert(std::is_trivially_copyable_v); - static_assert(std::is_same_v< - decltype(std::declval().report()), - AttestationReport>); + static_assert(std::is_aggregate_v); static_assert(std::is_same_v< decltype(Response::report_bytes), std::array>); @@ -431,14 +437,14 @@ TEST_CASE( testing::milan_attestation.end(), response.data.report_bytes.begin()); - ioctl6::detail::validate_report_size(response.data.report_size); - auto attestation = response.data; + CHECK(response.data.report_size == attestation_report_size); + const auto attestation = response.data; CHECK(std::equal( attestation.report_bytes.begin(), attestation.report_bytes.end(), testing::milan_attestation.begin(), testing::milan_attestation.end())); - auto report = attestation.report(); + auto report = parse_attestation_report_unverified(attestation.report_bytes); CHECK(tav_snp_attestation_report_version(report.get()) == 3); CHECK(response.sentinels_intact()); response.data = {}; @@ -448,17 +454,6 @@ TEST_CASE( testing::milan_attestation.begin(), testing::milan_attestation.end())); - attestation.report_bytes[0x050] ^= 1; - const auto updated_report = attestation.report(); - const uint8_t* data = nullptr; - size_t size = 0; - tav_snp_attestation_report_report_data(updated_report.get(), &data, &size); - REQUIRE(size == ccf::pal::snp_attestation_report_data_size); - CHECK(data[0] == attestation.report_bytes[0x050]); - tav_snp_attestation_report_report_data(report.get(), &data, &size); - REQUIRE(size == ccf::pal::snp_attestation_report_data_size); - CHECK(data[0] == testing::milan_attestation[0x050]); - for (auto* sentinels : {response.pre_sentinels, response.post_sentinels}) { for (size_t i = 0; i < response.num_sentinel_bytes; ++i) @@ -471,23 +466,6 @@ TEST_CASE( } } -TEST_CASE("SNP ioctl response bytes reject invalid report sizes") -{ - using namespace ccf::pal::snp; - for (const uint32_t report_size : - {0U, 1183U, 1185U, std::numeric_limits::max()}) - { - const auto expected_error = fmt::format( - "Unexpected SEV-SNP attestation report size: {} != {}", - report_size, - attestation_report_size); - CHECK_THROWS_WITH_AS( - ioctl6::detail::validate_report_size(report_size), - expected_error.c_str(), - std::logic_error); - } -} - TEST_CASE("SNP request rejects oversized report data before ioctl") { ccf::pal::PlatformAttestationReportData report_data; diff --git a/src/pal/test/snp_ioctl_test.cpp b/src/pal/test/snp_ioctl_test.cpp index 3c03ce4b1d92..01bc72ea50f9 100644 --- a/src/pal/test/snp_ioctl_test.cpp +++ b/src/pal/test/snp_ioctl_test.cpp @@ -25,7 +25,8 @@ TEST_CASE("SNP request attestation") PlatformAttestationReportData report_data(snp_report_data); const auto attestation = snp::request_attestation(report_data); REQUIRE(attestation.report_size == snp::attestation_report_size); - const auto report = attestation.report(); + const auto report = + snp::parse_attestation_report_unverified(attestation.report_bytes); const uint8_t* data = nullptr; size_t size = 0; From 87c673eb2ee53ba874bd22a753594aa3723fd0e5 Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Thu, 10 Sep 2026 14:51:48 +0100 Subject: [PATCH 10/14] chkpt --- CHANGELOG.md | 2 +- CMakeLists.txt | 18 -- include/ccf/pal/attestation_sev_snp.h | 4 +- include/ccf/pal/snp_ioctl.h | 15 +- include/ccf/pal/snp_ioctl6.h | 69 +++--- src/pal/quote_generation.h | 6 +- src/pal/test/snp_attestation_validation.cpp | 239 +++----------------- src/pal/test/snp_ioctl_test.cpp | 7 +- src/rust/test/native_link.c | 34 --- 9 files changed, 67 insertions(+), 327 deletions(-) delete mode 100644 src/rust/test/native_link.c diff --git a/CHANGELOG.md b/CHANGELOG.md index 7165aa755e84..539f2f2486fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed - SNP attestation reports now use TAV's C API with `ccf::pal::snp::AttestationReport` as an owning smart-pointer alias. Use `parse_attestation_report_unverified()` to decode reports and `ccf::pal::verify_snp_attestation_report_and_get()` to apply TAV verification and CCF's policy. TAV byte accessors borrow report storage; destroying or replacing the owner invalidates those views. The packed `ccf::pal::snp::Attestation` wire-layout type and its legacy accessors remain available but are deprecated. Azure cache and THIM endorsement requests now encode the reported TCB as 16 hexadecimal digits, preserving leading zeroes. (#8083) -- `ccf::pal::snp::request_attestation()` in `ccf/pal/snp_ioctl.h` returns an `AttestationResponse` by value after checking ioctl safety sentinels and report size. Its `report_bytes` member owns the unverified report bytes. Decode them explicitly with `parse_attestation_report_unverified(attestation.report_bytes)`. The legacy `get_attestation()` signature is unchanged. `AttestationInterface::get_raw()` and its ioctl implementation remain available but are deprecated. (#8083) +- `ccf::pal::snp::get_attestation(data)` in `ccf/pal/snp_ioctl.h` retains its `std::unique_ptr` return type. Call `get_raw()` to obtain an owned `std::vector` of unverified report bytes, then decode them explicitly with `parse_attestation_report_unverified()`. `get_raw()` is not deprecated. The deprecated `get()` remains available for legacy callers. Ioctl acquisition checks safety sentinels, and both accessors check report size. (#8083) - CBOR parsing now rejects composite (array or map) and tagged values used as map keys anywhere in the decoded document, including nested maps in optional COSE headers (#8297). ### Removed diff --git a/CMakeLists.txt b/CMakeLists.txt index 32c816a8492d..9e4a8fb2c110 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -937,24 +937,6 @@ if(BUILD_TESTS) PRIVATE ${CMAKE_THREAD_LIBS_INIT} ccfcrypto ) - # Do not link ccfcrypto: it can mask missing Rust native dependencies. - add_executable(ccf_rs_link_test src/rust/test/native_link.c) - target_include_directories( - ccf_rs_link_test - PRIVATE - ${CCF_DIR}/3rdparty/internal/tee-attestation-verification/ffi/include - ) - target_link_libraries(ccf_rs_link_test PRIVATE ccf_rs) - target_link_options( - ccf_rs_link_test - PRIVATE $<$:LINKER:--as-needed> - ) - add_warning_checks(ccf_rs_link_test) - add_san(ccf_rs_link_test) - add_test(NAME ccf_rs_link_test COMMAND ccf_rs_link_test) - set_property(TEST ccf_rs_link_test APPEND PROPERTY LABELS unit) - add_san_test_properties(ccf_rs_link_test) - add_unit_test(pem_test ${CMAKE_CURRENT_SOURCE_DIR}/src/crypto/test/pem.cpp) target_link_libraries(pem_test PRIVATE ${CMAKE_THREAD_LIBS_INIT}) diff --git a/include/ccf/pal/attestation_sev_snp.h b/include/ccf/pal/attestation_sev_snp.h index 6430b89e71b8..ccc907cacf98 100644 --- a/include/ccf/pal/attestation_sev_snp.h +++ b/include/ccf/pal/attestation_sev_snp.h @@ -702,12 +702,10 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== { public: [[deprecated( - "Use request_attestation().report_bytes from ccf/pal/snp_ioctl.h and " + "Use get_raw() and explicitly decode with " "parse_attestation_report_unverified")]] [[nodiscard]] virtual const snp:: Attestation& get() const = 0; - [[deprecated( - "Use request_attestation().report_bytes from ccf/pal/snp_ioctl.h")]] virtual std::vector get_raw() = 0; virtual ~AttestationInterface() = default; diff --git a/include/ccf/pal/snp_ioctl.h b/include/ccf/pal/snp_ioctl.h index b035fea8cf64..7a23eee47c46 100644 --- a/include/ccf/pal/snp_ioctl.h +++ b/include/ccf/pal/snp_ioctl.h @@ -6,24 +6,13 @@ namespace ccf::pal::snp { - using AttestationResponse = ioctl6::AttestationResponse; - static inline bool supports_sev_snp() { return ioctl6::supports_sev_snp(); } - // Request a fresh response with an owned, unverified report byte array. - static AttestationResponse request_attestation( - const PlatformAttestationReportData& report_data) - { - if (ioctl6::supports_sev_snp()) - { - return ioctl6::request_attestation(report_data); - } - throw std::logic_error("SEV-SNP not supported"); - } - + // Acquire an attestation object. get_raw() returns owned, unverified bytes; + // decode them explicitly with parse_attestation_report_unverified(). static std::unique_ptr get_attestation( const PlatformAttestationReportData& report_data) { diff --git a/include/ccf/pal/snp_ioctl6.h b/include/ccf/pal/snp_ioctl6.h index e5ea88d8eec0..0b6ffdce2f66 100644 --- a/include/ccf/pal/snp_ioctl6.h +++ b/include/ccf/pal/snp_ioctl6.h @@ -32,24 +32,23 @@ namespace ccf::pal::snp::ioctl6 // https://github.com/torvalds/linux/blob/v6.8/drivers/virt/coco/sev-guest/sev-guest.h // https://github.com/torvalds/linux/blob/v6.8/include/uapi/linux/sev-guest.h constexpr size_t ATTESTATION_RESPONSE_SIZE = 4000; + struct AttestationResponse + { + uint32_t status = 0; + uint32_t report_size = 0; + std::array reserved = {}; + std::array report_bytes = {}; + std::array< + uint8_t, + detail::ATTESTATION_RESPONSE_SIZE - 0x20 - attestation_report_size> + padding = {}; + }; + static_assert( + sizeof(AttestationResponse) == detail::ATTESTATION_RESPONSE_SIZE); + static_assert(offsetof(AttestationResponse, report_size) == 0x04); + static_assert(offsetof(AttestationResponse, report_bytes) == 0x20); } - struct AttestationResponse - { - uint32_t status = 0; - uint32_t report_size = 0; - std::array reserved = {}; - std::array report_bytes = {}; - std::array< - uint8_t, - detail::ATTESTATION_RESPONSE_SIZE - 0x20 - attestation_report_size> - padding = {}; - }; - static_assert( - sizeof(AttestationResponse) == detail::ATTESTATION_RESPONSE_SIZE); - static_assert(offsetof(AttestationResponse, report_size) == 0x04); - static_assert(offsetof(AttestationResponse, report_bytes) == 0x20); - #pragma pack(push, 1) // Helper to add padding to a struct, so that the resulting struct has some // minimum size. As a minor detail, the padding will be initialised to 0. @@ -144,7 +143,7 @@ namespace ccf::pal::snp::ioctl6 uint32_t status = 0; uint32_t report_size = 0; uint8_t reserved[0x20 - 0x8] = {0}; - [[deprecated("Use request_attestation().report_bytes")]] + [[deprecated("Use get_raw() and parse_attestation_report_unverified")]] snp::Attestation report = {}; uint8_t padding[64] = {0}; // padding to the size of SEV_SNP_REPORT_RSP_BUF_SZ (i.e., 1280 bytes) @@ -255,12 +254,10 @@ namespace ccf::pal::snp::ioctl6 namespace detail { - template - void request_attestation( + inline void request_attestation( const PlatformAttestationReportData& report_data, - IoctlSentinel& response) + IoctlSentinel& response) { - static_assert(sizeof(Response) == ATTESTATION_RESPONSE_SIZE); AttestationReq req = {}; if (report_data.data.size() <= snp_attestation_report_data_size) { @@ -283,7 +280,7 @@ namespace ccf::pal::snp::ioctl6 // Documented at // https://www.kernel.org/doc/html/latest/virt/coco/sev-guest.html - GuestRequest payload = { + AttestationRequest payload = { .req_data = &req, .resp_wrapper = &response.data, .exit_info = {0}}; int rc = ioctl(fd, SEV_SNP_GUEST_MSG_REPORT, &payload); @@ -309,37 +306,24 @@ namespace ccf::pal::snp::ioctl6 } } - // Validates ioctl safety and report size, not report authenticity. - static AttestationResponse request_attestation( - const PlatformAttestationReportData& report_data) - { - IoctlSentinel response; - detail::request_attestation(report_data, response); - if (response.data.report_size != attestation_report_size) - { - throw std::logic_error(fmt::format( - "Unexpected SEV-SNP attestation report size: {} != {}", - response.data.report_size, - attestation_report_size)); - } - return response.data; - } - class Attestation : public AttestationInterface { - IoctlSentinel resp_with_sentinel; - PaddedAttestationResp& padded_resp = resp_with_sentinel.data; + PaddedAttestationResp padded_resp; public: Attestation(const PlatformAttestationReportData& report_data) { - detail::request_attestation(report_data, resp_with_sentinel); + IoctlSentinel response; + detail::request_attestation(report_data, response); + // Retain legacy storage for the reference returned by get(). + static_assert(sizeof(padded_resp) == sizeof(response.data)); + std::memcpy(&padded_resp, &response.data, sizeof(padded_resp)); } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" [[deprecated( - "Use request_attestation().report_bytes and " + "Use get_raw() and explicitly decode with " "parse_attestation_report_unverified")]] [[nodiscard]] const ccf::pal:: snp::Attestation& get() const override @@ -354,7 +338,6 @@ namespace ccf::pal::snp::ioctl6 return padded_resp.report; } - [[deprecated("Use request_attestation().report_bytes")]] std::vector get_raw() override { if (padded_resp.report_size != attestation_report_size) diff --git a/src/pal/quote_generation.h b/src/pal/quote_generation.h index 3f5635b865e9..4c35101ad437 100644 --- a/src/pal/quote_generation.h +++ b/src/pal/quote_generation.h @@ -89,11 +89,9 @@ namespace ccf::pal { QuoteInfo node_quote_info = {}; node_quote_info.format = QuoteFormat::amd_sev_snp_v1; - auto attestation = snp::request_attestation(report_data); - node_quote_info.quote.assign( - attestation.report_bytes.begin(), attestation.report_bytes.end()); + node_quote_info.quote = snp::get_attestation(report_data)->get_raw(); auto report = - snp::parse_attestation_report_unverified(attestation.report_bytes); + snp::parse_attestation_report_unverified(node_quote_info.quote); if ( tav_snp_attestation_report_version(report.get()) < diff --git a/src/pal/test/snp_attestation_validation.cpp b/src/pal/test/snp_attestation_validation.cpp index f71509e5cec2..256099220e2b 100644 --- a/src/pal/test/snp_attestation_validation.cpp +++ b/src/pal/test/snp_attestation_validation.cpp @@ -131,38 +131,6 @@ namespace } } -TEST_CASE("unverified SNP report accessors") -{ - using namespace ccf::pal; - static_assert( - std:: - is_same_v); - static_assert(!std::is_copy_constructible_v); - static_assert(std::is_nothrow_move_constructible_v); - static_assert(std::is_same_v< - decltype(snp::parse_attestation_report_unverified( - std::declval>())), - snp::AttestationReport>); - - auto report = - snp::parse_attestation_report_unverified(snp::testing::milan_attestation); - - CHECK(tav_snp_attestation_report_version(report.get()) == 3); - CHECK(tav_snp_attestation_report_cpuid_fam_id(report.get()) == 25); - CHECK(tav_snp_attestation_report_cpuid_mod_id(report.get()) == 1); - const uint8_t* data = nullptr; - size_t size = 0; - tav_snp_attestation_report_reported_tcb(report.get(), &data, &size); - CHECK( - snp::TcbVersionRaw::from_span({data, size}).to_hex() == "db18000000000004"); - tav_snp_attestation_report_measurement(report.get(), &data, &size); - const auto measurement = std::span{data, size}; - auto moved_report = std::move(report); - CHECK(measurement.size() == snp_attestation_measurement_size); - tav_snp_attestation_report_signature_r(moved_report.get(), &data, &size); - CHECK(size == 72); -} - TEST_CASE("CCF policy is separate from generic TAV verification") { using namespace ccf::pal; @@ -224,50 +192,6 @@ TEST_CASE("unverified SNP report rejects invalid sizes") } } -TEST_CASE("SNP byte accessors borrow report storage") -{ - struct ByteField - { - void (*accessor)(const TavSnpAttestationReport*, const uint8_t**, size_t*); - size_t offset; - size_t size; - }; - const ByteField fields[] = { - {tav_snp_attestation_report_family_id, 0x010, 16}, - {tav_snp_attestation_report_image_id, 0x020, 16}, - {tav_snp_attestation_report_platform_version, 0x038, 8}, - {tav_snp_attestation_report_report_data, 0x050, 64}, - {tav_snp_attestation_report_measurement, 0x090, 48}, - {tav_snp_attestation_report_host_data, 0x0C0, 32}, - {tav_snp_attestation_report_id_key_digest, 0x0E0, 48}, - {tav_snp_attestation_report_author_key_digest, 0x110, 48}, - {tav_snp_attestation_report_report_id, 0x140, 32}, - {tav_snp_attestation_report_report_id_ma, 0x160, 32}, - {tav_snp_attestation_report_reported_tcb, 0x180, 8}, - {tav_snp_attestation_report_chip_id, 0x1A0, 64}, - {tav_snp_attestation_report_committed_tcb, 0x1E0, 8}, - {tav_snp_attestation_report_launch_tcb, 0x1F0, 8}, - {tav_snp_attestation_report_signature_r, 0x2A0, 72}, - {tav_snp_attestation_report_signature_s, 0x2E8, 72}}; - - const auto& raw_report = ccf::pal::snp::testing::milan_attestation; - auto report = ccf::pal::snp::parse_attestation_report_unverified(raw_report); - for (const auto& [accessor, offset, size] : fields) - { - const uint8_t* data = nullptr; - size_t length = 0; - accessor(report.get(), &data, &length); - const auto first = std::span{data, length}; - data = nullptr; - length = 0; - accessor(report.get(), &data, &length); - const auto second = std::span{data, length}; - CHECK(first.data() == second.data()); - REQUIRE(first.size() == size); - CHECK(std::equal(first.begin(), first.end(), raw_report.begin() + offset)); - } -} - TEST_CASE("SNP chip ID access rejects empty handles") { ccf::pal::snp::AttestationReport report; @@ -277,60 +201,24 @@ TEST_CASE("SNP chip ID access rejects empty handles") std::logic_error); } -TEST_CASE("SNP borrowed bytes survive report ownership transfers") +TEST_CASE("VCEK chip ID uses the product-specific prefix") { using namespace ccf::pal::snp; - auto raw_report = testing::milan_attestation; - std::optional original = - parse_attestation_report_unverified(raw_report); - const uint8_t* data = nullptr; - size_t size = 0; - tav_snp_attestation_report_measurement(original->get(), &data, &size); - const auto measurement = std::span{data, size}; - raw_report[0x090] ^= 0xff; - CHECK(measurement[0] == testing::milan_attestation[0x090]); - - auto moved = std::move(*original); - original.reset(); - tav_snp_attestation_report_measurement(moved.get(), &data, &size); - CHECK(measurement.data() == data); - CHECK(measurement.size() == size); - - auto assigned = - parse_attestation_report_unverified(testing::genoa_attestation); - assigned = std::move(moved); - tav_snp_attestation_report_measurement(assigned.get(), &data, &size); - CHECK(measurement.data() == data); - CHECK(measurement.size() == size); - CHECK(std::equal( - measurement.begin(), - measurement.end(), - testing::milan_attestation.begin() + 0x090)); -} - -TEST_CASE("VCEK chip ID borrows the product-specific prefix") -{ - using namespace ccf::pal::snp; - for (const auto* raw_report : - {&testing::milan_attestation, - &testing::genoa_attestation, - &testing::turin_attestation}) + struct TestCase + { + const std::vector& report; + size_t chip_id_size; + }; + for (const auto& [raw_report, expected_size] : + {TestCase{testing::milan_attestation, 64}, + TestCase{testing::genoa_attestation, 64}, + TestCase{testing::turin_attestation, 8}}) { - auto report = parse_attestation_report_unverified(*raw_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{data, size}; + auto report = parse_attestation_report_unverified(raw_report); const auto vcek_chip_id = get_chip_id_for_vcek(report); - CHECK(vcek_chip_id.data() == chip_id.data()); - CHECK( - vcek_chip_id.size() == - (get_sev_snp_product( - tav_snp_attestation_report_cpuid_fam_id(report.get()), - tav_snp_attestation_report_cpuid_mod_id(report.get())) == - ProductName::Turin ? - 8 : - 64)); + REQUIRE(vcek_chip_id.size() == expected_size); + CHECK(std::equal( + vcek_chip_id.begin(), vcek_chip_id.end(), raw_report.begin() + 0x1A0)); } } @@ -393,85 +281,12 @@ TEST_CASE("SNP verification rejects other quote formats before parsing") } } -TEST_CASE( - "SNP ioctl response owns report bytes separately from header and padding") -{ - using namespace ccf::pal::snp; - static_assert( - std::is_same_v< - decltype(request_attestation( - std::declval())), - AttestationResponse>); - static_assert( - std::is_same_v< - decltype(ioctl6::request_attestation( - std::declval())), - AttestationResponse>); - - using Response = AttestationResponse; - static_assert(std::is_standard_layout_v); - static_assert(std::is_trivially_copyable_v); - static_assert(std::is_aggregate_v); - static_assert(std::is_same_v< - decltype(Response::report_bytes), - std::array>); - static_assert(sizeof(Response) == 4000); - static_assert(offsetof(Response, status) == 0); - static_assert(offsetof(Response, report_size) == 4); - static_assert(offsetof(Response, reserved) == 8); - static_assert(offsetof(Response, report_bytes) == 0x20); - static_assert(offsetof(Response, padding) == 0x20 + attestation_report_size); - - ioctl6::IoctlSentinel response; - CHECK(response.data.status == 0); - CHECK(response.data.report_size == 0); - CHECK(response.data.reserved == decltype(response.data.reserved){}); - CHECK(response.data.report_bytes == decltype(response.data.report_bytes){}); - CHECK(response.data.padding == decltype(response.data.padding){}); - response.data.status = 0xa5a5a5a5; - response.data.report_size = attestation_report_size; - response.data.reserved.fill(0xa5); - response.data.padding.fill(0xa5); - std::copy( - testing::milan_attestation.begin(), - testing::milan_attestation.end(), - response.data.report_bytes.begin()); - - CHECK(response.data.report_size == attestation_report_size); - const auto attestation = response.data; - CHECK(std::equal( - attestation.report_bytes.begin(), - attestation.report_bytes.end(), - testing::milan_attestation.begin(), - testing::milan_attestation.end())); - auto report = parse_attestation_report_unverified(attestation.report_bytes); - CHECK(tav_snp_attestation_report_version(report.get()) == 3); - CHECK(response.sentinels_intact()); - response.data = {}; - CHECK(std::equal( - attestation.report_bytes.begin(), - attestation.report_bytes.end(), - testing::milan_attestation.begin(), - testing::milan_attestation.end())); - - for (auto* sentinels : {response.pre_sentinels, response.post_sentinels}) - { - for (size_t i = 0; i < response.num_sentinel_bytes; ++i) - { - sentinels[i] ^= 1; - CHECK_FALSE(response.sentinels_intact()); - sentinels[i] ^= 1; - CHECK(response.sentinels_intact()); - } - } -} - TEST_CASE("SNP request rejects oversized report data before ioctl") { ccf::pal::PlatformAttestationReportData report_data; report_data.data.resize(ccf::pal::snp_attestation_report_data_size + 1); CHECK_THROWS_WITH_AS( - ccf::pal::snp::ioctl6::request_attestation(report_data), + ccf::pal::snp::ioctl6::Attestation{report_data}, "User-defined report data is larger than available space", std::logic_error); } @@ -517,6 +332,23 @@ TEST_CASE("legacy SNP report layout remains compatible") CHECK(offsetof(Attestation, chip_id) == 0x1A0); CHECK(offsetof(Attestation, signature) == 0x2A0); + ccf::pal::snp::ioctl6::detail::AttestationResponse response; + response.report_size = ccf::pal::snp::attestation_report_size; + std::copy( + ccf::pal::snp::testing::milan_attestation.begin(), + ccf::pal::snp::testing::milan_attestation.end(), + response.report_bytes.begin()); + ccf::pal::snp::ioctl6::PaddedAttestationResp legacy_response; + static_assert(std::is_trivially_copyable_v); + static_assert(sizeof(legacy_response) == sizeof(response)); + std::memcpy(&legacy_response, &response, sizeof(legacy_response)); + CHECK(legacy_response.report_size == response.report_size); + CHECK( + std::memcmp( + &legacy_response.report, + response.report_bytes.data(), + response.report_bytes.size()) == 0); + report.version = ccf::pal::snp::minimum_attestation_version; report.cpuid_fam_id = 0x19; report.cpuid_mod_id = 0x01; @@ -542,13 +374,6 @@ TEST_CASE("milan validation") pal::PlatformAttestationMeasurement measurement; pal::PlatformAttestationReportData report_data; - static_assert(std::is_same_v< - decltype(pal::verify_snp_attestation_report_and_get( - std::declval(), - std::declval(), - std::declval())), - pal::snp::AttestationReport>); - const auto report = pal::verify_snp_attestation_report_and_get( milan_quote_info, measurement, report_data); REQUIRE(report != nullptr); diff --git a/src/pal/test/snp_ioctl_test.cpp b/src/pal/test/snp_ioctl_test.cpp index 01bc72ea50f9..2cd0d3779f15 100644 --- a/src/pal/test/snp_ioctl_test.cpp +++ b/src/pal/test/snp_ioctl_test.cpp @@ -23,10 +23,9 @@ TEST_CASE("SNP request attestation") snp_report_data.report_data.begin(), snp_report_data.report_data.end(), 0); PlatformAttestationReportData report_data(snp_report_data); - const auto attestation = snp::request_attestation(report_data); - REQUIRE(attestation.report_size == snp::attestation_report_size); - const auto report = - snp::parse_attestation_report_unverified(attestation.report_bytes); + const auto attestation = snp::get_attestation(report_data)->get_raw(); + REQUIRE(attestation.size() == snp::attestation_report_size); + const auto report = snp::parse_attestation_report_unverified(attestation); const uint8_t* data = nullptr; size_t size = 0; diff --git a/src/rust/test/native_link.c b/src/rust/test/native_link.c deleted file mode 100644 index 97eeb2384f8e..000000000000 --- a/src/rust/test/native_link.c +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the Apache 2.0 License. - -#include "tav/snp.h" - -#include - -int main(void) -{ - const uint8_t bytes[1184] = {0}; - TavSnpAttestationReport* report = NULL; - TavError* error = tav_snp_attestation_report_from_unverified_bytes( - bytes, sizeof(bytes), &report); - if (error != NULL) - { - fprintf(stderr, "TAV parsing failed: %s\n", tav_error_message(error)); - tav_error_free(error); - tav_snp_attestation_report_free(report); - return 1; - } - if (report == NULL) - { - fputs("TAV parsing returned no report\n", stderr); - return 1; - } - const uint32_t version = tav_snp_attestation_report_version(report); - tav_snp_attestation_report_free(report); - if (version != 0) - { - fputs("TAV parsing returned an unexpected report version\n", stderr); - return 1; - } - return 0; -} From 6e185347e8338362edd9b956b66ab066613abac4 Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Fri, 11 Sep 2026 13:21:28 +0100 Subject: [PATCH 11/14] snagging --- include/ccf/pal/attestation_sev_snp.h | 4 ++++ .../rpc/test/internal_tables_access_test.cpp | 11 ++++++++++ src/pal/test/snp_attestation_validation.cpp | 22 +++++++++++++++++++ src/service/internal_tables_access.h | 4 ++++ tests/npm_tests.py | 6 +++++ 5 files changed, 47 insertions(+) diff --git a/include/ccf/pal/attestation_sev_snp.h b/include/ccf/pal/attestation_sev_snp.h index ccc907cacf98..89b990ef247a 100644 --- a/include/ccf/pal/attestation_sev_snp.h +++ b/include/ccf/pal/attestation_sev_snp.h @@ -540,6 +540,10 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== const AttestationReport& quote, const snp::EndorsementsServers& endorsements_servers = {}) { + 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) diff --git a/src/node/rpc/test/internal_tables_access_test.cpp b/src/node/rpc/test/internal_tables_access_test.cpp index 42b90fed27e5..486e0e22e00c 100644 --- a/src/node/rpc/test/internal_tables_access_test.cpp +++ b/src/node/rpc/test/internal_tables_access_test.cpp @@ -89,6 +89,17 @@ TEST_CASE("direct node deletion updates consensus configuration") REQUIRE(consensus.configuration_changes == 1); } +TEST_CASE("trust_node_snp_tcb_version rejects an empty owner") +{ + ccf::kv::Store kv_store; + auto tx = kv_store.create_tx(); + const pal::snp::AttestationReport report; + CHECK_THROWS_WITH_AS( + InternalTablesAccess::trust_node_snp_tcb_version(tx, report), + "Cannot access an empty SNP attestation report", + std::logic_error); +} + TEST_CASE("trust_node_uvm_endorsements - not recovering, empty map") { ccf::kv::Store kv_store; diff --git a/src/pal/test/snp_attestation_validation.cpp b/src/pal/test/snp_attestation_validation.cpp index 256099220e2b..8fa2609b2ffd 100644 --- a/src/pal/test/snp_attestation_validation.cpp +++ b/src/pal/test/snp_attestation_validation.cpp @@ -201,6 +201,28 @@ TEST_CASE("SNP chip ID access rejects empty handles") std::logic_error); } +TEST_CASE("SNP endorsement configuration rejects empty owners") +{ + using namespace ccf::pal::snp; + AttestationReport report; + + SUBCASE("default constructed") {} + + SUBCASE("moved from") + { + report = parse_attestation_report_unverified(testing::milan_attestation); + auto owner = std::move(report); + REQUIRE(owner != nullptr); + CHECK_NOTHROW(make_endorsement_endpoint_configuration(owner)); + } + + REQUIRE(report == nullptr); + CHECK_THROWS_WITH_AS( + make_endorsement_endpoint_configuration(report), + "Cannot access an empty SNP attestation report", + std::logic_error); +} + TEST_CASE("VCEK chip ID uses the product-specific prefix") { using namespace ccf::pal::snp; diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index 1ac661ad2a30..22cd8cc50f56 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -966,6 +966,10 @@ namespace ccf static void trust_node_snp_tcb_version( ccf::kv::Tx& tx, const pal::snp::AttestationReport& attestation) { + if (attestation == nullptr) + { + throw std::logic_error("Cannot access an empty SNP attestation report"); + } if ( tav_snp_attestation_report_version(attestation.get()) < pal::snp::minimum_attestation_version) diff --git a/tests/npm_tests.py b/tests/npm_tests.py index ff7877d09dc5..9311c7c8df86 100644 --- a/tests/npm_tests.py +++ b/tests/npm_tests.py @@ -746,6 +746,12 @@ def test_npm_app(network, args): assert r.status_code == http.HTTPStatus.OK, r.status_code report_json = r.body.json()["attestation"] print(f"{report_json=}") + raw_report = b64decode(reference_quote["raw"]) + expected_current_build = raw_report[0x1E8] + expected_current_minor = raw_report[0x1E9] + assert expected_current_build != expected_current_minor + assert report_json["current_build"] == expected_current_build + assert report_json["current_minor"] == expected_current_minor assert report_json[ "report_data" ] == "7a6a68c0a2b85b8aae00ca04f644831680222f44167e5558a9e072b70c60e958" + ( From ba5d021ac46c5e4839bdaa21bda8b4c1a78a062f Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Fri, 11 Sep 2026 13:25:54 +0100 Subject: [PATCH 12/14] changelogging --- CHANGELOG.md | 16 +++++++++++++--- python/pyproject.toml | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29acc1f9fdbd..cecd6e5697f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,25 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [7.0.16] + +[7.0.16]: https://github.com/microsoft/CCF/releases/tag/ccf-7.0.16 + +### Fixed + +- Fixed swapped `current_minor` and `current_build` values in JavaScript `verifySnpAttestation()` results. These fields now match the AMD SEV-SNP report layout. The deprecated C++ `ccf::pal::snp::Attestation` retains its previous field mapping for compatibility. (#8083) + +### Changed + +- SNP attestation reports now use TAV's C API with `ccf::pal::snp::AttestationReport` as an owning smart-pointer alias. Use `parse_attestation_report_unverified()` to decode reports and `ccf::pal::verify_snp_attestation_report_and_get()` to apply TAV verification and CCF's policy. TAV byte accessors borrow report storage; destroying or replacing the owner invalidates those views. The packed `ccf::pal::snp::Attestation` wire-layout type and its legacy accessors remain available but are deprecated. Azure cache and THIM endorsement requests now encode the reported TCB as 16 hexadecimal digits, preserving leading zeroes. (#8083) +- `ccf::pal::snp::get_attestation(data)` in `ccf/pal/snp_ioctl.h` retains its `std::unique_ptr` return type. Call `get_raw()` to obtain an owned `std::vector` of unverified report bytes, then decode them explicitly with `parse_attestation_report_unverified()`. `get_raw()` is not deprecated. The deprecated `get()` remains available for legacy callers. Ioctl acquisition checks safety sentinels, and both accessors check report size. (#8083) + ## [7.0.15] [7.0.15]: https://github.com/microsoft/CCF/releases/tag/ccf-7.0.15 ### Fixed -- Fixed swapped `current_minor` and `current_build` values in JavaScript `verifySnpAttestation()` results. These fields now match the AMD SEV-SNP report layout. The deprecated C++ `ccf::pal::snp::Attestation` retains its previous field mapping for compatibility. (#8083) - Strengthened access checks on JavaScript KV handles, including namespace restrictions in the historical KV (#8318). - Invalid PEM construction and JSON deserialisation errors no longer include the supplied data, which may contain private key material (#8330). - Reaching the soft session cap on an unsecured RPC interface no longer terminates the node by attempting a TLS handshake without a certificate. (#8331) @@ -19,8 +31,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed -- SNP attestation reports now use TAV's C API with `ccf::pal::snp::AttestationReport` as an owning smart-pointer alias. Use `parse_attestation_report_unverified()` to decode reports and `ccf::pal::verify_snp_attestation_report_and_get()` to apply TAV verification and CCF's policy. TAV byte accessors borrow report storage; destroying or replacing the owner invalidates those views. The packed `ccf::pal::snp::Attestation` wire-layout type and its legacy accessors remain available but are deprecated. Azure cache and THIM endorsement requests now encode the reported TCB as 16 hexadecimal digits, preserving leading zeroes. (#8083) -- `ccf::pal::snp::get_attestation(data)` in `ccf/pal/snp_ioctl.h` retains its `std::unique_ptr` return type. Call `get_raw()` to obtain an owned `std::vector` of unverified report bytes, then decode them explicitly with `parse_attestation_report_unverified()`. `get_raw()` is not deprecated. The deprecated `get()` remains available for legacy callers. Ioctl acquisition checks safety sentinels, and both accessors check report size. (#8083) - Updated QuickJS to `2026-06-04`, with isolated build-time patches for out-of-memory backtrace handling and enforcement of lowered heap limits (#8340). - CBOR parsing now rejects composite (array or map) and tagged values used as map keys anywhere in the decoded document, including nested maps in optional COSE headers (#8297). diff --git a/python/pyproject.toml b/python/pyproject.toml index 50017d2f73df..900ffe42262c 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "ccf" -version = "7.0.15" +version = "7.0.16" authors = [ { name="CCF Team", email="CCF-Sec@microsoft.com" }, ] From a9336d89b443790a256bc06fa310e1e0b79f5b40 Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Fri, 11 Sep 2026 17:38:37 +0100 Subject: [PATCH 13/14] Construct raw TCB versions directly from spans Replace the vector constructor and from_span factory with a span constructor. Direct vector construction remains supported; implicit vector conversion is intentionally no longer supported. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e97cc978-adf6-4072-bd60-c1d9b51096d8 --- include/ccf/pal/attestation_sev_snp.h | 18 +++++---------- src/js/extensions/snp_attestation.cpp | 3 +-- src/node/node_state.h | 4 ++-- src/node/quote.cpp | 3 +-- src/pal/attestation.cpp | 6 ++--- src/pal/test/snp_attestation_validation.cpp | 22 +++++++++++++------ ...erify_uvm_attestation_and_endorsements.cpp | 2 +- src/service/internal_tables_access.h | 2 +- 8 files changed, 29 insertions(+), 31 deletions(-) diff --git a/include/ccf/pal/attestation_sev_snp.h b/include/ccf/pal/attestation_sev_snp.h index 89b990ef247a..742fcc2d72d8 100644 --- a/include/ccf/pal/attestation_sev_snp.h +++ b/include/ccf/pal/attestation_sev_snp.h @@ -231,21 +231,14 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== TcbVersionRaw() = default; - TcbVersionRaw(const std::vector& data) : - TcbVersionRaw(from_span(data)) - {} - - static TcbVersionRaw from_span(std::span data) + TcbVersionRaw(std::span data) { if (data.size() != snp_tcb_version_size) { throw std::logic_error( fmt::format("Invalid TCB version raw data size: {}", data.size())); } - TcbVersionRaw tcb_version; - std::memcpy( - tcb_version.underlying_data, data.data(), snp_tcb_version_size); - return tcb_version; + std::memcpy(underlying_data, data.data(), snp_tcb_version_size); } [[nodiscard]] std::vector data() const @@ -622,7 +615,7 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== case ProductName::Milan: case ProductName::Genoa: { - auto tcb = TcbVersionRaw::from_span(reported_tcb_raw) + auto tcb = TcbVersionRaw(reported_tcb_raw) .to_policy(product) .to_milan_genoa(); boot_loader = fmt::format("{}", tcb.boot_loader); @@ -633,9 +626,8 @@ pRb21iI1NlNCfOGUPIhVpWECAwEAAQ== } case ProductName::Turin: { - auto tcb = TcbVersionRaw::from_span(reported_tcb_raw) - .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); diff --git a/src/js/extensions/snp_attestation.cpp b/src/js/extensions/snp_attestation.cpp index 3d054395d611..aa24eb15a898 100644 --- a/src/js/extensions/snp_attestation.cpp +++ b/src/js/extensions/snp_attestation.cpp @@ -24,8 +24,7 @@ namespace ccf::js::extensions JSValue make_js_tcb_version( js::core::Context& jsctx, std::span tcb) { - auto data_hex = - jsctx.new_string(pal::snp::TcbVersionRaw::from_span(tcb).to_hex()); + auto data_hex = jsctx.new_string(pal::snp::TcbVersionRaw(tcb).to_hex()); JS_CHECK_EXC(data_hex); return data_hex.take(); } diff --git a/src/node/node_state.h b/src/node/node_state.h index e5fb5cffc871..9c91c512e4c1 100644 --- a/src/node/node_state.h +++ b/src/node/node_state.h @@ -888,7 +888,7 @@ namespace ccf size_t size = 0; tav_snp_attestation_report_reported_tcb( snp_attestation.value().get(), &data, &size); - snp_tcb_version = ccf::pal::snp::TcbVersionRaw::from_span({data, size}); + snp_tcb_version = ccf::pal::snp::TcbVersionRaw({data, size}); } // Verify that the security policy matches the quoted digest of the policy @@ -1044,7 +1044,7 @@ namespace ccf tav_snp_attestation_report_reported_tcb( report.get(), &data, &size); const auto reported_tcb = - ccf::pal::snp::TcbVersionRaw::from_span({data, size}); + ccf::pal::snp::TcbVersionRaw({data, size}); // tcbm is a single hex value, like DB18000000000004. auto tcb_as_hex = reported_tcb.to_hex(); diff --git a/src/node/quote.cpp b/src/node/quote.cpp index 1b6d6eb65b91..9606d60571b9 100644 --- a/src/node/quote.cpp +++ b/src/node/quote.cpp @@ -340,8 +340,7 @@ namespace ccf size_t size = 0; tav_snp_attestation_report_reported_tcb(attestation.get(), &data, &size); auto attestation_tcb_policy = - pal::snp::TcbVersionRaw::from_span({data, size}) - .to_policy(product_family); + pal::snp::TcbVersionRaw({data, size}).to_policy(product_family); if (pal::snp::TcbVersionPolicy::is_valid( min_tcb_opt.value(), attestation_tcb_policy)) diff --git a/src/pal/attestation.cpp b/src/pal/attestation.cpp index 2e365f56957d..29f1e7167c37 100644 --- a/src/pal/attestation.cpp +++ b/src/pal/attestation.cpp @@ -373,8 +373,8 @@ namespace ccf::pal if (endorsed_tcb.has_value()) { auto endorsed_tcb_policy = endorsed_tcb->to_policy(product_family); - auto reported_tcb = snp::TcbVersionRaw::from_span(reported_tcb_raw) - .to_policy(product_family); + auto reported_tcb = + snp::TcbVersionRaw(reported_tcb_raw).to_policy(product_family); if (!snp::TcbVersionPolicy::is_valid(endorsed_tcb_policy, reported_tcb)) { @@ -408,7 +408,7 @@ namespace ccf::pal auto raw_endorsed_tcb = snp::TcbVersionRaw::from_hex(quote_info.endorsed_tcb.value()); - const auto reported_tcb = snp::TcbVersionRaw::from_span(reported_tcb_raw); + const auto reported_tcb = snp::TcbVersionRaw(reported_tcb_raw); if (raw_endorsed_tcb != reported_tcb) { auto endorsed_tcb_hex = raw_endorsed_tcb.to_hex(); diff --git a/src/pal/test/snp_attestation_validation.cpp b/src/pal/test/snp_attestation_validation.cpp index 8fa2609b2ffd..fa81db653961 100644 --- a/src/pal/test/snp_attestation_validation.cpp +++ b/src/pal/test/snp_attestation_validation.cpp @@ -248,15 +248,23 @@ TEST_CASE("TCB values can be constructed from borrowed bytes") { using ccf::pal::snp::TcbVersionRaw; std::array bytes = {4, 0, 0, 0, 0, 0, 24, 219}; - const auto tcb = TcbVersionRaw::from_span(bytes); + const auto tcb = TcbVersionRaw(std::span(bytes)); CHECK(tcb.to_hex() == "db18000000000004"); CHECK(tcb == TcbVersionRaw(std::vector(bytes.begin(), bytes.end()))); bytes.fill(0); CHECK(tcb.to_hex() == "db18000000000004"); - CHECK_THROWS_WITH_AS( - TcbVersionRaw::from_span(std::span(bytes).first(7)), - "Invalid TCB version raw data size: 7", - std::logic_error); + for (const size_t size : {0, 7, 9}) + { + const std::vector invalid_bytes(size); + const auto expected_error = + fmt::format("Invalid TCB version raw data size: {}", size); + CHECK_THROWS_WITH_AS( + TcbVersionRaw{invalid_bytes}, expected_error.c_str(), std::logic_error); + CHECK_THROWS_WITH_AS( + TcbVersionRaw(std::span(invalid_bytes)), + expected_error.c_str(), + std::logic_error); + } } TEST_CASE("SNP verification preserves invalid size error") @@ -555,7 +563,7 @@ TEST_CASE("Parsing tcb versions from attestaion") size_t size = 0; tav_snp_attestation_report_reported_tcb( milan_attestation.get(), &data, &size); - auto milan_tcb = ccf::pal::snp::TcbVersionRaw::from_span({data, size}) + auto milan_tcb = ccf::pal::snp::TcbVersionRaw({data, size}) .to_policy(ccf::pal::snp::ProductName::Milan) .to_milan_genoa(); CHECK_EQ(milan_tcb.microcode, 0xdb); @@ -868,7 +876,7 @@ TEST_CASE("Extracting metadata from endorsements") tav_snp_attestation_report_reported_tcb(attestation.get(), &data, &size); CHECK_EQ( nlohmann::json(endorsed_tcb.value()).dump(), - nlohmann::json(pal::snp::TcbVersionRaw::from_span({data, size})).dump()); + nlohmann::json(pal::snp::TcbVersionRaw({data, size})).dump()); auto endorsed_chip_id = pal::get_endorsed_chip_id_from_cert(chip_certificate); REQUIRE(endorsed_chip_id.has_value()); diff --git a/src/pal/test/verify_uvm_attestation_and_endorsements.cpp b/src/pal/test/verify_uvm_attestation_and_endorsements.cpp index 6932390b1972..aa9c981ead73 100644 --- a/src/pal/test/verify_uvm_attestation_and_endorsements.cpp +++ b/src/pal/test/verify_uvm_attestation_and_endorsements.cpp @@ -249,7 +249,7 @@ int main(int argc, char** argv) attestation_unverified.get(), &data, &size); validate_endorsements( endorsements, - ccf::pal::snp::TcbVersionRaw::from_span({data, size}), + ccf::pal::snp::TcbVersionRaw({data, size}), quote_info.endorsements); LOG_INFO_FMT("Verifying quote"); diff --git a/src/service/internal_tables_access.h b/src/service/internal_tables_access.h index 22cd8cc50f56..7e6e3cae63e1 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -1006,7 +1006,7 @@ namespace ccf tav_snp_attestation_report_reported_tcb(attestation.get(), &data, &size); h->put( cpuid.hex_str(), - pal::snp::TcbVersionRaw::from_span({data, size}).to_policy(product)); + pal::snp::TcbVersionRaw({data, size}).to_policy(product)); } static void init_configuration( From 1af71fc0ad9c8b86679607cd6d2d98e437d3d33d Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Fri, 11 Sep 2026 17:48:39 +0100 Subject: [PATCH 14/14] Correct legacy SNP build and minor field mapping Align the deprecated C++ report fields with AMD offsets and cover them with fixture assertions. Shorten the changelog by removing the TCB encoding entry. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e97cc978-adf6-4072-bd60-c1d9b51096d8 --- CHANGELOG.md | 3 +-- include/ccf/pal/attestation_sev_snp.h | 4 ++-- src/pal/test/snp_attestation_validation.cpp | 9 ++++++++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2228c9d7df8..3bf3cce3c236 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,13 +12,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Fixed - 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()` returned swapped `current_minor` and `current_build` values. They now match the AMD SEV-SNP report layout. The deprecated C++ `ccf::pal::snp::Attestation` keeps its previous field mapping (#8083). +- 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). -- Azure cache and THIM endorsement requests now encode the reported TCB as 16 hexadecimal digits, preserving leading zeroes (#8083). ## [7.0.15] diff --git a/include/ccf/pal/attestation_sev_snp.h b/include/ccf/pal/attestation_sev_snp.h index 742fcc2d72d8..fafae2660b17 100644 --- a/include/ccf/pal/attestation_sev_snp.h +++ b/include/ccf/pal/attestation_sev_snp.h @@ -437,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 */ diff --git a/src/pal/test/snp_attestation_validation.cpp b/src/pal/test/snp_attestation_validation.cpp index fa81db653961..6cd2aa48eee2 100644 --- a/src/pal/test/snp_attestation_validation.cpp +++ b/src/pal/test/snp_attestation_validation.cpp @@ -323,7 +323,7 @@ TEST_CASE("SNP request rejects oversized report data before ioctl") #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" -TEST_CASE("legacy SNP report layout remains compatible") +TEST_CASE("legacy SNP report layout matches the AMD specification") { using ccf::pal::snp::Attestation; @@ -354,12 +354,16 @@ TEST_CASE("legacy SNP report layout remains compatible") Attestation report = {}; CHECK(sizeof(report) == ccf::pal::snp::attestation_report_size); + CHECK(alignof(Attestation) == 1); CHECK(offsetof(Attestation, version) == 0x000); CHECK(offsetof(Attestation, policy) == 0x008); CHECK(offsetof(Attestation, report_data) == 0x050); CHECK(offsetof(Attestation, measurement) == 0x090); CHECK(offsetof(Attestation, reported_tcb) == 0x180); CHECK(offsetof(Attestation, chip_id) == 0x1A0); + CHECK(offsetof(Attestation, current_build) == 0x1E8); + CHECK(offsetof(Attestation, current_minor) == 0x1E9); + CHECK(offsetof(Attestation, current_major) == 0x1EA); CHECK(offsetof(Attestation, signature) == 0x2A0); ccf::pal::snp::ioctl6::detail::AttestationResponse response; @@ -373,6 +377,9 @@ TEST_CASE("legacy SNP report layout remains compatible") static_assert(sizeof(legacy_response) == sizeof(response)); std::memcpy(&legacy_response, &response, sizeof(legacy_response)); CHECK(legacy_response.report_size == response.report_size); + REQUIRE(response.report_bytes[0x1E8] != response.report_bytes[0x1E9]); + CHECK(legacy_response.report.current_build == response.report_bytes[0x1E8]); + CHECK(legacy_response.report.current_minor == response.report_bytes[0x1E9]); CHECK( std::memcmp( &legacy_response.report,