1+ // Copyright (c) Microsoft Corporation. All rights reserved.
2+ // Licensed under the Apache 2.0 License.
3+
4+ #include " verify_uvm_attestation_and_endorsements.h"
5+
6+ #include " ccf/ds/quote_info.h"
7+ #include " node/uvm_endorsements.h"
8+
9+ std::string read_in (const std::string& path)
10+ {
11+ auto expanded_path = ccf::env::expand_envvars_in_path (path);
12+ LOG_TRACE_FMT (" Reading from: {}" , expanded_path);
13+ auto str_opt = files::try_slurp_string (expanded_path);
14+ if (!str_opt.has_value ())
15+ {
16+ throw std::invalid_argument (" Could not read from: " + expanded_path);
17+ }
18+ return str_opt.value ();
19+ }
20+
21+ void validate_endorsements (
22+ const std::string& snp_endorsements,
23+ const ccf::pal::snp::TcbVersionRaw& attested_tcb,
24+ std::vector<uint8_t >& endorsements_pem)
25+ {
26+ const auto raw_data = ccf::crypto::raw_from_b64 (snp_endorsements);
27+
28+ const auto j = nlohmann::json::parse (raw_data);
29+ const auto aci_endorsements = j.get <ccf::pal::snp::ACIReportEndorsements>();
30+
31+ // tcbm is a single hex value, like DB18000000000004. To match
32+ // that with a TcbVersion, reverse the bytes.
33+ const auto * tcb_begin = reinterpret_cast <const uint8_t *>(&attested_tcb);
34+ const std::span<const uint8_t > tcb_bytes{
35+ tcb_begin, tcb_begin + sizeof (attested_tcb)};
36+ auto tcb_as_hex =
37+ fmt::format (" {:02x}" , fmt::join (tcb_bytes.rbegin (), tcb_bytes.rend (), " " ));
38+ ccf::nonstd::to_upper (tcb_as_hex);
39+
40+ if (tcb_as_hex == aci_endorsements.tcbm )
41+ {
42+ LOG_INFO_FMT (
43+ " Using SNP endorsements loaded from file, endorsing TCB {}" , tcb_as_hex);
44+ }
45+ else
46+ {
47+ throw std::runtime_error (fmt::format (
48+ " SNP endorsements loaded from disk contained tcbm {}, which does not "
49+ " match reported TCB of current attestation {}. " ,
50+ aci_endorsements.tcbm ,
51+ tcb_as_hex));
52+ }
53+
54+ endorsements_pem.clear ();
55+ endorsements_pem.insert (
56+ endorsements_pem.end (),
57+ aci_endorsements.vcek_cert .begin (),
58+ aci_endorsements.vcek_cert .end ());
59+ endorsements_pem.insert (
60+ endorsements_pem.end (),
61+ aci_endorsements.certificate_chain .begin (),
62+ aci_endorsements.certificate_chain .end ());
63+ }
64+
65+ // Verify that the security policy matches the quoted digest of the policy
66+ void validate_security_policy (
67+ const ccf::QuoteInfo& quote_info, const std::string& security_policy)
68+ {
69+ auto quoted_digest = ccf::AttestationProvider::get_host_data (quote_info);
70+ if (!quoted_digest.has_value ())
71+ {
72+ throw std::logic_error (" Unable to find host data in attestation" );
73+ }
74+
75+ auto security_policy_digest =
76+ quote_info.format == ccf::QuoteFormat::amd_sev_snp_v1 ?
77+ ccf::crypto::Sha256Hash (ccf::crypto::raw_from_b64 (security_policy)) :
78+ ccf::crypto::Sha256Hash (security_policy);
79+ if (security_policy_digest != quoted_digest.value ())
80+ {
81+ throw std::logic_error (fmt::format (
82+ " Digest of decoded security policy \" {}\" {} does not match "
83+ " attestation host data {}" ,
84+ security_policy,
85+ security_policy_digest.hex_str (),
86+ quoted_digest.value ().hex_str ()));
87+ }
88+ LOG_INFO_FMT (
89+ " Successfully verified attested security policy {}" ,
90+ security_policy_digest);
91+ }
92+
93+ void validate_uvm_endorsements (
94+ ccf::QuoteInfo& quote_info, const std::string& uvm_endorsements)
95+ {
96+ try
97+ {
98+ auto uvm_endorsements_raw = ccf::crypto::raw_from_b64 (uvm_endorsements);
99+ auto snp_uvm_endorsements = ccf::pal::verify_uvm_endorsements_descriptor (
100+ uvm_endorsements_raw,
101+ ccf::AttestationProvider::get_measurement (quote_info).value ());
102+ LOG_INFO_FMT (
103+ " Successfully verified attested UVM endorsements: {}" ,
104+ snp_uvm_endorsements.to_str ());
105+ quote_info.uvm_endorsements = uvm_endorsements_raw;
106+ }
107+ catch (const std::exception& e)
108+ {
109+ throw std::logic_error (
110+ fmt::format (" Error verifying UVM endorsements: {}" , e.what ()));
111+ }
112+ }
113+
114+ // CCF's join policy both authenticates the quote and checks it against
115+ // reference values in the CCF store. Here we only authenticate the quote.
116+ // Each step that would check against the store is commented out and replaced
117+ // with the steps which do not require the store.
118+ ccf::QuoteVerificationResult validate_join_policy (
119+ ccf::QuoteInfo quote_info, std::vector<uint8_t > expected_pubk_der)
120+ {
121+ ccf::pal::PlatformAttestationMeasurement measurement = {};
122+ ccf::crypto::Sha256Hash quoted_hash;
123+ ccf::pal::PlatformAttestationReportData report_data;
124+ try
125+ {
126+ ccf::pal::verify_quote (quote_info, measurement, report_data);
127+ quoted_hash = report_data.to_sha256_hash ();
128+ }
129+ catch (const std::exception& e)
130+ {
131+ LOG_FAIL_FMT (" Failed to verify attestation report: {}" , e.what ());
132+ return ccf::QuoteVerificationResult::Failed;
133+ }
134+
135+ // auto rc = verify_host_data_against_store(tx, quote_info);
136+
137+ // rc = verify_enclave_measurement_against_store(
138+ ccf::verify_uvm_endorsements_against_roots_of_trust (
139+ quote_info.uvm_endorsements .value (),
140+ measurement,
141+ ccf::default_uvm_roots_of_trust);
142+
143+ // rc = verify_tcb_version_against_store(tx, quote_info);
144+
145+ auto rc = ccf::verify_quoted_node_public_key (expected_pubk_der, quoted_hash);
146+
147+ return rc;
148+ }
149+
150+ int main (int argc, char ** argv)
151+ {
152+ if (!ccf::pal::snp::supports_sev_snp ())
153+ {
154+ std::cout << " Skipping test as this is not running in SEV-SNP" << std::endl;
155+ return 0 ;
156+ }
157+ ccf::logger::config::level () = ccf::LoggerLevel::TRACE;
158+ ccf::logger::config::add_text_console_logger ();
159+
160+ if (argc < 2 || argc > 4 )
161+ {
162+ std::cout << " Usage <program> <snp_endorsements_path> "
163+ " [uvm_endorsements_path] [security_policy_path]"
164+ << std::endl;
165+ return 1 ;
166+ }
167+
168+ const std::string endorsements_path = argv[1 ];
169+
170+ std::optional<std::string> uvm_endorsements_path;
171+ if (argc >= 3 )
172+ {
173+ uvm_endorsements_path = argv[2 ];
174+ }
175+
176+ std::optional<std::string> security_policy_path = std::nullopt ;
177+ if (argc >= 4 )
178+ {
179+ security_policy_path = argv[3 ];
180+ }
181+
182+ try
183+ {
184+ LOG_INFO_FMT (" Reading SNP endorsements from: {}" , endorsements_path);
185+ std::string endorsements = read_in (endorsements_path);
186+
187+ LOG_INFO_FMT (
188+ " Reading SNP UVM endorsements from: {}" , uvm_endorsements_path);
189+ std::optional<std::string> uvm_endorsements = std::nullopt ;
190+ if (uvm_endorsements_path.has_value ())
191+ {
192+ uvm_endorsements = read_in (uvm_endorsements_path.value ());
193+ }
194+ else
195+ {
196+ LOG_INFO_FMT (" No UVM endorsements provided, skipping" );
197+ }
198+
199+ std::optional<std::string> security_policy = std::nullopt ;
200+ if (security_policy_path.has_value ())
201+ {
202+ LOG_INFO_FMT (
203+ " Reading SNP security policy from: {}" , security_policy_path);
204+ security_policy = read_in (security_policy_path.value ());
205+ }
206+ else
207+ {
208+ LOG_INFO_FMT (" No SNP security policy provided, skipping" );
209+ }
210+
211+ LOG_INFO_FMT (" Generating attestation" );
212+
213+ // generate private key
214+ ccf::crypto::KeyPair_OpenSSL node_sign_kp (ccf::crypto::CurveID::SECP384R1);
215+ ccf::pal::PlatformAttestationReportData report_data =
216+ ccf::crypto::Sha256Hash (node_sign_kp.public_key_der ());
217+ ccf::pal::generate_quote (
218+ report_data,
219+ [&](
220+ const ccf::QuoteInfo& qi,
221+ const ccf::pal::snp::EndorsementEndpointsConfiguration&
222+ /* endpoint_config*/ ) {
223+ ccf::QuoteInfo quote_info = qi;
224+
225+ CCF_ASSERT_FMT (
226+ quote_info.format == ccf::QuoteFormat::amd_sev_snp_v1,
227+ " Expected SNP quote format" );
228+
229+ LOG_INFO_FMT (" Verifying endorsements" );
230+ const auto * attestation_unverified =
231+ reinterpret_cast <const ccf::pal::snp::Attestation*>(
232+ quote_info.quote .data ());
233+ validate_endorsements (
234+ endorsements,
235+ attestation_unverified->reported_tcb ,
236+ quote_info.endorsements );
237+
238+ LOG_INFO_FMT (" Verifying quote" );
239+ ccf::pal::PlatformAttestationMeasurement d = {};
240+ ccf::pal::PlatformAttestationReportData r = {};
241+ ccf::pal::verify_quote (quote_info, d, r);
242+
243+ LOG_INFO_FMT (" Verifying security policy" );
244+ if (security_policy.has_value ())
245+ {
246+ validate_security_policy (quote_info, security_policy.value ());
247+ }
248+
249+ LOG_INFO_FMT (" Verifying UVM endorsements" );
250+ if (uvm_endorsements.has_value ())
251+ {
252+ validate_uvm_endorsements (quote_info, uvm_endorsements.value ());
253+ }
254+
255+ LOG_INFO_FMT (" Running join policy validation" );
256+ auto rc =
257+ validate_join_policy (quote_info, node_sign_kp.public_key_der ());
258+ if (rc != ccf::QuoteVerificationResult::Verified)
259+ {
260+ throw std::logic_error (
261+ fmt::format (" Join policy validation failed: {}" , (int )rc));
262+ }
263+ },
264+ {});
265+ }
266+ catch (const std::invalid_argument& e)
267+ {
268+ LOG_FAIL_FMT (" Error: {}" , e.what ());
269+ return 2 ;
270+ }
271+ LOG_INFO_FMT (" Successfully verified attestation and endorsements." );
272+ return 0 ;
273+ }
0 commit comments