From b96a3d6d62a4041f78a376ed9f9af22823996055 Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Wed, 15 Jul 2026 11:55:31 +0100 Subject: [PATCH 1/4] 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 8e5283c7d927..9b1fa1b0cd0c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -270,7 +270,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 60e1d4b3fc474f7481765c76f5f19350f2c428c7 Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Wed, 5 Aug 2026 17:32:59 +0100 Subject: [PATCH 2/4] 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 68f4a428cf0930cbf24f82e88d15a90f0f5ef21c Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Thu, 6 Aug 2026 18:04:33 +0100 Subject: [PATCH 3/4] bump tav --- .../tee-attestation-verification/CHANGELOG.md | 24 ++++++++ .../tee-attestation-verification/Cargo.lock | 10 ++-- .../attestation/Cargo.toml | 4 +- .../caci/Cargo.toml | 8 +-- .../cose/Cargo.toml | 4 +- .../crypto/Cargo.toml | 2 +- .../ffi/Cargo.toml | 10 ++-- .../ffi/include/tav/caci.h | 4 ++ .../ffi/include/tav/snp.h | 36 ++++++++---- .../ffi/src/c_ffi/snp.rs | 55 ++++++++++++++----- 10 files changed, 115 insertions(+), 42 deletions(-) diff --git a/3rdparty/internal/tee-attestation-verification/CHANGELOG.md b/3rdparty/internal/tee-attestation-verification/CHANGELOG.md index c0ae9dba1346..f82a150baa56 100644 --- a/3rdparty/internal/tee-attestation-verification/CHANGELOG.md +++ b/3rdparty/internal/tee-attestation-verification/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## [1.0.7] + +[1.0.7]: https://github.com/microsoft/TEE-Attestation-Verification/releases/tag/tav-1.0.7 + +### Added + +- C and .NET FFI constructors for decoding SNP reports without verification. (#101) + +## [1.0.6] + +[1.0.6]: https://github.com/microsoft/TEE-Attestation-Verification/releases/tag/tav-1.0.6 + +### Changed + +- Updated the NuGet package README to consume C-ACI's published endorsement formats directly. (#98) + +## [1.0.5] + +[1.0.5]: https://github.com/microsoft/TEE-Attestation-Verification/releases/tag/tav-1.0.5 + +### Added + +- Nuget packaging (#94) + ## [1.0.4] [1.0.4]: https://github.com/microsoft/TEE-Attestation-Verification/releases/tag/tav-1.0.4 diff --git a/3rdparty/internal/tee-attestation-verification/Cargo.lock b/3rdparty/internal/tee-attestation-verification/Cargo.lock index 3e44f17c2472..535fb9402c9b 100644 --- a/3rdparty/internal/tee-attestation-verification/Cargo.lock +++ b/3rdparty/internal/tee-attestation-verification/Cargo.lock @@ -1037,7 +1037,7 @@ dependencies = [ [[package]] name = "tee-attestation-verification-caci" -version = "1.0.4" +version = "1.0.7" dependencies = [ "serde_json", "tee-attestation-verification-cose", @@ -1049,7 +1049,7 @@ dependencies = [ [[package]] name = "tee-attestation-verification-cose" -version = "1.0.4" +version = "1.0.7" dependencies = [ "cborrs", "cborrs-nondet", @@ -1058,7 +1058,7 @@ dependencies = [ [[package]] name = "tee-attestation-verification-crypto" -version = "1.0.4" +version = "1.0.7" dependencies = [ "ecdsa", "foreign-types", @@ -1081,7 +1081,7 @@ dependencies = [ [[package]] name = "tee-attestation-verification-ffi" -version = "1.0.4" +version = "1.0.7" dependencies = [ "js-sys", "serde_json", @@ -1096,7 +1096,7 @@ dependencies = [ [[package]] name = "tee-attestation-verification-lib" -version = "1.0.4" +version = "1.0.7" dependencies = [ "console_error_panic_hook", "curl", diff --git a/3rdparty/internal/tee-attestation-verification/attestation/Cargo.toml b/3rdparty/internal/tee-attestation-verification/attestation/Cargo.toml index 83bef0aac4a7..2e068fa1d029 100644 --- a/3rdparty/internal/tee-attestation-verification/attestation/Cargo.toml +++ b/3rdparty/internal/tee-attestation-verification/attestation/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tee-attestation-verification-lib" -version = "1.0.4" +version = "1.0.7" edition = "2021" rust-version = "1.77" repository = "https://github.com/microsoft/TEE-Attestation-Verification" @@ -45,7 +45,7 @@ env_logger = "0.11" tokio = { version = "1", features = ["rt-multi-thread", "macros"] } [dependencies] -crypto = { package = "tee-attestation-verification-crypto", version = "1.0.4", path = "../crypto", default-features = false } +crypto = { package = "tee-attestation-verification-crypto", version = "1.0.7", path = "../crypto", default-features = false } zerocopy = {version = "0.8.31", features = ["derive"]} # KDS (online certificate fetching) dependencies diff --git a/3rdparty/internal/tee-attestation-verification/caci/Cargo.toml b/3rdparty/internal/tee-attestation-verification/caci/Cargo.toml index 4e2398856552..55780956f3bc 100644 --- a/3rdparty/internal/tee-attestation-verification/caci/Cargo.toml +++ b/3rdparty/internal/tee-attestation-verification/caci/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tee-attestation-verification-caci" -version = "1.0.4" +version = "1.0.7" edition = "2021" rust-version = "1.77" repository = "https://github.com/microsoft/TEE-Attestation-Verification" @@ -32,9 +32,9 @@ crypto_webcrypto = [ ] [dependencies] -attestation = { package = "tee-attestation-verification-lib", version = "1.0.4", path = "../attestation", default-features = false } -cose = { package = "tee-attestation-verification-cose", version = "1.0.4", path = "../cose", default-features = false } -crypto = { package = "tee-attestation-verification-crypto", version = "1.0.4", path = "../crypto", default-features = false } +attestation = { package = "tee-attestation-verification-lib", version = "1.0.7", path = "../attestation", default-features = false } +cose = { package = "tee-attestation-verification-cose", version = "1.0.7", path = "../cose", default-features = false } +crypto = { package = "tee-attestation-verification-crypto", version = "1.0.7", path = "../crypto", default-features = false } serde_json = "1" [target.'cfg(not(target_family = "wasm"))'.dev-dependencies] diff --git a/3rdparty/internal/tee-attestation-verification/cose/Cargo.toml b/3rdparty/internal/tee-attestation-verification/cose/Cargo.toml index 8ea8d462e7ca..4bdbaa16981d 100644 --- a/3rdparty/internal/tee-attestation-verification/cose/Cargo.toml +++ b/3rdparty/internal/tee-attestation-verification/cose/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tee-attestation-verification-cose" -version = "1.0.4" +version = "1.0.7" edition = "2021" rust-version = "1.77" repository = "https://github.com/microsoft/TEE-Attestation-Verification" @@ -16,7 +16,7 @@ crypto_pure_rust = ["crypto/crypto_pure_rust"] crypto_webcrypto = ["crypto/crypto_webcrypto"] [dependencies] -crypto = { package = "tee-attestation-verification-crypto", version = "1.0.4", path = "../crypto", default-features = false } +crypto = { package = "tee-attestation-verification-crypto", version = "1.0.7", path = "../crypto", default-features = false } # project-everest/everparse tag v2026.07.02 resolves to this pinned commit. cborrs = { git = "https://github.com/project-everest/everparse.git", rev = "950bc93838ac2faae51126d8acd0637cf8c8a569" } cborrs-nondet = { git = "https://github.com/project-everest/everparse.git", rev = "950bc93838ac2faae51126d8acd0637cf8c8a569" } diff --git a/3rdparty/internal/tee-attestation-verification/crypto/Cargo.toml b/3rdparty/internal/tee-attestation-verification/crypto/Cargo.toml index 1cef11a7e66e..2d29ccf0939b 100644 --- a/3rdparty/internal/tee-attestation-verification/crypto/Cargo.toml +++ b/3rdparty/internal/tee-attestation-verification/crypto/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tee-attestation-verification-crypto" -version = "1.0.4" +version = "1.0.7" edition = "2021" rust-version = "1.77" repository = "https://github.com/microsoft/TEE-Attestation-Verification" diff --git a/3rdparty/internal/tee-attestation-verification/ffi/Cargo.toml b/3rdparty/internal/tee-attestation-verification/ffi/Cargo.toml index d3be082a3b6f..d82f0ff76500 100644 --- a/3rdparty/internal/tee-attestation-verification/ffi/Cargo.toml +++ b/3rdparty/internal/tee-attestation-verification/ffi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tee-attestation-verification-ffi" -version = "1.0.4" +version = "1.0.7" edition = "2021" rust-version = "1.77" repository = "https://github.com/microsoft/TEE-Attestation-Verification" @@ -33,10 +33,10 @@ crypto_webcrypto = [ ] [dependencies] -attestation = { package = "tee-attestation-verification-lib", version = "1.0.4", path = "../attestation", default-features = false } -caci = { package = "tee-attestation-verification-caci", version = "1.0.4", path = "../caci", default-features = false } -cose = { package = "tee-attestation-verification-cose", version = "1.0.4", path = "../cose", default-features = false } -crypto = { package = "tee-attestation-verification-crypto", version = "1.0.4", path = "../crypto", default-features = false } +attestation = { package = "tee-attestation-verification-lib", version = "1.0.7", path = "../attestation", default-features = false } +caci = { package = "tee-attestation-verification-caci", version = "1.0.7", path = "../caci", default-features = false } +cose = { package = "tee-attestation-verification-cose", version = "1.0.7", path = "../cose", default-features = false } +crypto = { package = "tee-attestation-verification-crypto", version = "1.0.7", path = "../crypto", default-features = false } serde_json = "1" zerocopy = { version = "0.8.31", features = ["derive"] } diff --git a/3rdparty/internal/tee-attestation-verification/ffi/include/tav/caci.h b/3rdparty/internal/tee-attestation-verification/ffi/include/tav/caci.h index 0f20a89d5912..624e622eff50 100644 --- a/3rdparty/internal/tee-attestation-verification/ffi/include/tav/caci.h +++ b/3rdparty/internal/tee-attestation-verification/ffi/include/tav/caci.h @@ -57,6 +57,10 @@ TAV_CACI_API TavError *tav_verify_caci_uvm_endorsement( /* * Verify the relying-party CACI policy over staged verified artifacts. * + * attestation must be a report returned by tav_verify_snp_attestation. The + * caller is responsible for not passing a report created by + * tav_snp_attestation_report_from_unverified_bytes. + * * The minimum TCB policy is passed as two parallel arrays of minimum_tcb_count * entries: minimum_tcb_cpuids holds one uint32_t CPUID per entry, and * minimum_tcb_values holds minimum_tcb_count contiguous 8-byte TCB values (the diff --git a/3rdparty/internal/tee-attestation-verification/ffi/include/tav/snp.h b/3rdparty/internal/tee-attestation-verification/ffi/include/tav/snp.h index 0f33cfb0485e..2b452518954d 100644 --- a/3rdparty/internal/tee-attestation-verification/ffi/include/tav/snp.h +++ b/3rdparty/internal/tee-attestation-verification/ffi/include/tav/snp.h @@ -20,18 +20,19 @@ extern "C" { * * Usage summary: * - Call tav_verify_snp_attestation with the raw attestation report and the - * ARK, ASK, and VCEK certificates in PEM format. - * - On success, verification writes a TavSnpAttestationReport* to out_report. + * ARK, ASK, and VCEK certificates in PEM format, or call + * tav_snp_attestation_report_from_unverified_bytes for fixed-size decoding + * without authentication or semantic validation. + * - On success, either function writes a TavSnpAttestationReport* to out_report. * Pass that report handle to the tav_snp_attestation_report_* accessors. * - Free the report handle with tav_snp_attestation_report_free when finished. * * Error behavior: - * - tav_verify_snp_attestation returns NULL on success, or an owned TavError* - * on failure. Inspect failures with tav_error_code and tav_error_message, - * then free them with tav_error_free. - * - tav_verify_snp_attestation reports invalid verification inputs and invalid - * out_report state as TavError failures. Each input buffer is capped at - * 1 GiB. + * - Both report constructors return NULL on success, or an owned TavError* on + * failure. Inspect failures with tav_error_code and tav_error_message, then + * free them with tav_error_free. + * - Both constructors report invalid inputs and invalid out_report state as + * TavError failures. Each input buffer is capped at 1 GiB. * - Error accessors are defensive for NULL TavError pointers: tav_error_code * returns TAV_ERROR_IS_NULL and tav_error_message returns a static * diagnostic string. @@ -63,7 +64,22 @@ TAV_API TavError *tav_verify_snp_attestation( size_t vcek_pem_len, TavSnpAttestationReport **out_report); -/* Scalar report accessors. Invalid report pointers are undefined behavior. */ +/* + * Parse an SNP attestation report without cryptographic verification. + * + * This checks only that the input has the fixed SNP report size. It does not + * validate field values, reserved bytes, signatures, certificates, or TCBs. + * Do not make trust decisions from the returned report. + * + * out_report must point to a writable report-handle slot. The slot is set to + * NULL before any fallible work and set to an owned handle only on success. + */ +TAV_API TavError *tav_snp_attestation_report_from_unverified_bytes( + const uint8_t *report_bytes, + size_t report_len, + TavSnpAttestationReport **out_report); + +/* Report accessors. Invalid report pointers are undefined behavior. */ TAV_API uint32_t tav_snp_attestation_report_version( const TavSnpAttestationReport *report); TAV_API uint32_t tav_snp_attestation_report_guest_svn( @@ -196,7 +212,7 @@ TAV_API void tav_snp_attestation_report_signature_s( const uint8_t **data, size_t *len); -/* Frees a report handle returned by tav_verify_snp_attestation. NULL is a no-op. */ +/* Frees a report handle returned by either report constructor. NULL is a no-op. */ TAV_API void tav_snp_attestation_report_free(TavSnpAttestationReport *report); #ifdef __cplusplus diff --git a/3rdparty/internal/tee-attestation-verification/ffi/src/c_ffi/snp.rs b/3rdparty/internal/tee-attestation-verification/ffi/src/c_ffi/snp.rs index ba358da60430..da246e7d9173 100644 --- a/3rdparty/internal/tee-attestation-verification/ffi/src/c_ffi/snp.rs +++ b/3rdparty/internal/tee-attestation-verification/ffi/src/c_ffi/snp.rs @@ -5,9 +5,12 @@ //! //! This module exports the symbols declared in `ffi/include/tav/snp.h`. //! -//! [`tav_verify_snp_attestation`] returns a null [`TavError`] pointer on -//! success and an owned [`TavError`] pointer on failure. On success it -//! writes an owned [`TavSnpAttestationReport`] handle to `out_report`. +//! [`tav_verify_snp_attestation`] and +//! [`tav_snp_attestation_report_from_unverified_bytes`] return a null +//! [`TavError`] pointer on success and an owned [`TavError`] pointer on failure. +//! On success they write an owned [`TavSnpAttestationReport`] handle to +//! `out_report`. The latter performs fixed-size decoding only; its handle must +//! not be used where a cryptographically verified report is required. //! Callers release these handles with [`crate::c_ffi::utils::tav_error_free`] and //! [`tav_snp_attestation_report_free`]. //! @@ -39,11 +42,20 @@ fn tav_error_from_verification_error(error: VerificationError) -> TavError { TavError::new(code, error.to_string()) } +fn parse_report(report_bytes: &[u8]) -> Result<&AttestationReport, TavError> { + AttestationReport::ref_from_bytes(report_bytes).map_err(|_| { + TavError::invalid_argument(format!( + "Invalid attestation report: expected {} bytes, got {}", + std::mem::size_of::(), + report_bytes.len() + )) + }) +} + impl TavSnpAttestationReport { pub fn report(&self) -> &AttestationReport { - AttestationReport::ref_from_bytes(&self.bytes).expect( - "TavSnpAttestationReport is only constructed from verified bytes so parsing should not fail", - ) + AttestationReport::ref_from_bytes(&self.bytes) + .expect("TavSnpAttestationReport is only constructed from exact-size bytes") } } @@ -94,13 +106,7 @@ pub unsafe extern "C" fn tav_verify_snp_attestation( let report_bytes = unsafe { input_bytes(report_bytes, report_len, "attestation report", false) }?; - let report = AttestationReport::ref_from_bytes(report_bytes).map_err(|_| { - TavError::invalid_argument(format!( - "Invalid attestation report: expected {} bytes, got {}", - std::mem::size_of::(), - report_len - )) - })?; + let report = parse_report(report_bytes)?; let ark_pem = unsafe { input_bytes(ark_pem, ark_pem_len, "ARK", false) }?; let ark = certificate_from_pem(ark_pem).map_err(|error| { @@ -137,6 +143,29 @@ pub unsafe extern "C" fn tav_verify_snp_attestation( }) } +#[no_mangle] +pub unsafe extern "C" fn tav_snp_attestation_report_from_unverified_bytes( + report_bytes: *const u8, + report_len: usize, + out_report: *mut *mut TavSnpAttestationReport, +) -> *mut TavError { + into_result(|| { + unsafe { owned_out_ptr(out_report, "out_report") }?; + + let report_bytes = + unsafe { input_bytes(report_bytes, report_len, "attestation report", false) }?; + parse_report(report_bytes)?; + + let report = TavSnpAttestationReport { + bytes: report_bytes.to_vec(), + }; + unsafe { + *out_report = Box::into_raw(Box::new(report)); + } + Ok(()) + }) +} + scalar_accessor!(tav_snp_attestation_report_version, u32, |report| report .version .get()); From 06c01721a1e1238f352062cd6b8fd70e279da623 Mon Sep 17 00:00:00 2001 From: cjen1-msft Date: Thu, 6 Aug 2026 18:04:44 +0100 Subject: [PATCH 4/4] 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 dd83857594dd..3854054a5dd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,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 9b1fa1b0cd0c..52c2f811260f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -604,6 +604,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 caf4357872a8..70b41c6146d6 100644 --- a/src/node/node_state.h +++ b/src/node/node_state.h @@ -740,7 +740,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 @@ -882,19 +882,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 c5858dbc1806..b75b9ec24f27 100644 --- a/src/service/internal_tables_access.h +++ b/src/service/internal_tables_access.h @@ -930,35 +930,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(