From 792c9d0ec1e0a867811558c88fd649eefee58e32 Mon Sep 17 00:00:00 2001 From: Xynnn007 Date: Mon, 29 Jan 2024 11:36:06 +0800 Subject: [PATCH 1/2] AA: add check_init_data API This api is used to check the current TEE evidence's initdata field, like TDX's MRCONFIGID, SNP's HOSTDATA, etc. If the one inside evidence is different from the one provided, the RPC will raise an error. The operation of getting current TEE's evidence should be network independent, because AA usually runs at an early stage of a guest, where network is usually not prepared. Signed-off-by: Xynnn007 --- Cargo.lock | 1 + .../app/src/rpc/attestation/mod.rs | 62 ++- attestation-agent/attester/src/lib.rs | 4 + .../token_provider/aa/attestation_agent.rs | 389 +++++++++++++++++- .../aa/attestation_agent_ttrpc.rs | 22 + attestation-agent/lib/Cargo.toml | 1 + attestation-agent/lib/src/lib.rs | 15 +- .../protos/attestation-agent.proto | 12 + 8 files changed, 494 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 990e52628..9045329e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -262,6 +262,7 @@ dependencies = [ "resource_uri", "serde", "serde_json", + "sha2 0.10.8", "strum", "thiserror", "tokio", diff --git a/attestation-agent/app/src/rpc/attestation/mod.rs b/attestation-agent/app/src/rpc/attestation/mod.rs index 2e7e8e2a0..2f5bc537f 100644 --- a/attestation-agent/app/src/rpc/attestation/mod.rs +++ b/attestation-agent/app/src/rpc/attestation/mod.rs @@ -21,8 +21,9 @@ pub mod grpc { AttestationAgentService, AttestationAgentServiceServer, }; use attestation::{ - ExtendRuntimeMeasurementRequest, ExtendRuntimeMeasurementResponse, GetEvidenceRequest, - GetEvidenceResponse, GetTokenRequest, GetTokenResponse, + CheckInitDataRequest, CheckInitDataResponse, ExtendRuntimeMeasurementRequest, + ExtendRuntimeMeasurementResponse, GetEvidenceRequest, GetEvidenceResponse, GetTokenRequest, + GetTokenResponse, }; use std::net::SocketAddr; use tonic::{transport::Server, Request, Response, Status}; @@ -116,6 +117,35 @@ pub mod grpc { Result::Ok(Response::new(reply)) } + + async fn check_init_data( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + + let attestation_agent_mutex_clone = Arc::clone(&ASYNC_ATTESTATION_AGENT); + let mut attestation_agent = attestation_agent_mutex_clone.lock().await; + + debug!("Call AA to check init data ..."); + + attestation_agent + .check_init_data(&request.digest) + .await + .map_err(|e| { + error!("Call AA to check init data failed: {}", e); + Status::internal(format!( + "[ERROR:{}] AA check init data failed: {}", + AGENT_NAME, e + )) + })?; + + debug!("Check init data successfully!"); + + let reply = CheckInitDataResponse {}; + + Result::Ok(Response::new(reply)) + } } pub async fn start_grpc_service(socket: SocketAddr) -> Result<()> { @@ -236,6 +266,34 @@ pub mod ttrpc { let reply = attestation_agent::ExtendRuntimeMeasurementResponse::new(); ::ttrpc::Result::Ok(reply) } + + async fn check_init_data( + &self, + _ctx: &::ttrpc::r#async::TtrpcContext, + req: attestation_agent::CheckInitDataRequest, + ) -> ::ttrpc::Result { + debug!("Call AA to check initdata ..."); + + let attestation_agent_mutex_clone = ASYNC_ATTESTATION_AGENT.clone(); + let mut attestation_agent = attestation_agent_mutex_clone.lock().await; + + attestation_agent + .check_init_data(&req.Digest) + .await + .map_err(|e| { + error!("Call AA to check initdata failed: {}", e); + let mut error_status = ::ttrpc::proto::Status::new(); + error_status.set_code(Code::INTERNAL); + error_status.set_message(format!( + "[ERROR:{}] AA check initdata failed: {}", + AGENT_NAME, e + )); + ::ttrpc::Error::RpcStatus(error_status) + })?; + + let reply = attestation_agent::CheckInitDataResponse::new(); + ::ttrpc::Result::Ok(reply) + } } pub fn start_ttrpc_service() -> Result> { diff --git a/attestation-agent/attester/src/lib.rs b/attestation-agent/attester/src/lib.rs index 741f18c71..0d47a678d 100644 --- a/attestation-agent/attester/src/lib.rs +++ b/attestation-agent/attester/src/lib.rs @@ -74,6 +74,10 @@ pub trait Attester { ) -> Result<()> { bail!("Unimplemented") } + + async fn check_init_data(&self, _init_data: &[u8]) -> Result<()> { + bail!("Unimplemented"); + } } // Detect which TEE platform the KBC running environment is. diff --git a/attestation-agent/kbs_protocol/src/token_provider/aa/attestation_agent.rs b/attestation-agent/kbs_protocol/src/token_provider/aa/attestation_agent.rs index ff1163576..77852ccb1 100644 --- a/attestation-agent/kbs_protocol/src/token_provider/aa/attestation_agent.rs +++ b/attestation-agent/kbs_protocol/src/token_provider/aa/attestation_agent.rs @@ -756,6 +756,371 @@ impl ::protobuf::reflect::ProtobufValue for ExtendRuntimeMeasurementResponse { type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; } +// @@protoc_insertion_point(message:attestation_agent.InitDataPlaintext) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct InitDataPlaintext { + // message fields + // @@protoc_insertion_point(field:attestation_agent.InitDataPlaintext.Content) + pub Content: ::std::vec::Vec, + // @@protoc_insertion_point(field:attestation_agent.InitDataPlaintext.Algorithm) + pub Algorithm: ::std::string::String, + // special fields + // @@protoc_insertion_point(special_field:attestation_agent.InitDataPlaintext.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a InitDataPlaintext { + fn default() -> &'a InitDataPlaintext { + ::default_instance() + } +} + +impl InitDataPlaintext { + pub fn new() -> InitDataPlaintext { + ::std::default::Default::default() + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(2); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "Content", + |m: &InitDataPlaintext| { &m.Content }, + |m: &mut InitDataPlaintext| { &mut m.Content }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "Algorithm", + |m: &InitDataPlaintext| { &m.Algorithm }, + |m: &mut InitDataPlaintext| { &mut m.Algorithm }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "InitDataPlaintext", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for InitDataPlaintext { + const NAME: &'static str = "InitDataPlaintext"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.Content = is.read_bytes()?; + }, + 18 => { + self.Algorithm = is.read_string()?; + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if !self.Content.is_empty() { + my_size += ::protobuf::rt::bytes_size(1, &self.Content); + } + if !self.Algorithm.is_empty() { + my_size += ::protobuf::rt::string_size(2, &self.Algorithm); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if !self.Content.is_empty() { + os.write_bytes(1, &self.Content)?; + } + if !self.Algorithm.is_empty() { + os.write_string(2, &self.Algorithm)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> InitDataPlaintext { + InitDataPlaintext::new() + } + + fn clear(&mut self) { + self.Content.clear(); + self.Algorithm.clear(); + self.special_fields.clear(); + } + + fn default_instance() -> &'static InitDataPlaintext { + static instance: InitDataPlaintext = InitDataPlaintext { + Content: ::std::vec::Vec::new(), + Algorithm: ::std::string::String::new(), + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for InitDataPlaintext { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("InitDataPlaintext").unwrap()).clone() + } +} + +impl ::std::fmt::Display for InitDataPlaintext { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for InitDataPlaintext { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:attestation_agent.CheckInitDataRequest) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct CheckInitDataRequest { + // message fields + // @@protoc_insertion_point(field:attestation_agent.CheckInitDataRequest.Digest) + pub Digest: ::std::vec::Vec, + // special fields + // @@protoc_insertion_point(special_field:attestation_agent.CheckInitDataRequest.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a CheckInitDataRequest { + fn default() -> &'a CheckInitDataRequest { + ::default_instance() + } +} + +impl CheckInitDataRequest { + pub fn new() -> CheckInitDataRequest { + ::std::default::Default::default() + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(1); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "Digest", + |m: &CheckInitDataRequest| { &m.Digest }, + |m: &mut CheckInitDataRequest| { &mut m.Digest }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "CheckInitDataRequest", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for CheckInitDataRequest { + const NAME: &'static str = "CheckInitDataRequest"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.Digest = is.read_bytes()?; + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + if !self.Digest.is_empty() { + my_size += ::protobuf::rt::bytes_size(1, &self.Digest); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + if !self.Digest.is_empty() { + os.write_bytes(1, &self.Digest)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> CheckInitDataRequest { + CheckInitDataRequest::new() + } + + fn clear(&mut self) { + self.Digest.clear(); + self.special_fields.clear(); + } + + fn default_instance() -> &'static CheckInitDataRequest { + static instance: CheckInitDataRequest = CheckInitDataRequest { + Digest: ::std::vec::Vec::new(), + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for CheckInitDataRequest { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("CheckInitDataRequest").unwrap()).clone() + } +} + +impl ::std::fmt::Display for CheckInitDataRequest { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for CheckInitDataRequest { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +// @@protoc_insertion_point(message:attestation_agent.CheckInitDataResponse) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct CheckInitDataResponse { + // special fields + // @@protoc_insertion_point(special_field:attestation_agent.CheckInitDataResponse.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a CheckInitDataResponse { + fn default() -> &'a CheckInitDataResponse { + ::default_instance() + } +} + +impl CheckInitDataResponse { + pub fn new() -> CheckInitDataResponse { + ::std::default::Default::default() + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(0); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "CheckInitDataResponse", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for CheckInitDataResponse { + const NAME: &'static str = "CheckInitDataResponse"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> CheckInitDataResponse { + CheckInitDataResponse::new() + } + + fn clear(&mut self) { + self.special_fields.clear(); + } + + fn default_instance() -> &'static CheckInitDataResponse { + static instance: CheckInitDataResponse = CheckInitDataResponse { + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for CheckInitDataResponse { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("CheckInitDataResponse").unwrap()).clone() + } +} + +impl ::std::fmt::Display for CheckInitDataResponse { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for CheckInitDataResponse { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + static file_descriptor_proto_data: &'static [u8] = b"\ \n\x17attestation-agent.proto\x12\x11attestation_agent\"6\n\x12GetEviden\ ceRequest\x12\x20\n\x0bRuntimeData\x18\x01\x20\x01(\x0cR\x0bRuntimeData\ @@ -765,13 +1130,18 @@ static file_descriptor_proto_data: &'static [u8] = b"\ \x20\x01(\x0cR\x05Token\"v\n\x1fExtendRuntimeMeasurementRequest\x12\x16\ \n\x06Events\x18\x01\x20\x03(\x0cR\x06Events\x12)\n\rRegisterIndex\x18\ \x02\x20\x01(\x04H\0R\rRegisterIndex\x88\x01\x01B\x10\n\x0e_RegisterInde\ - x\"\"\n\x20ExtendRuntimeMeasurementResponse2\xd2\x02\n\x17AttestationAge\ - ntService\x12\\\n\x0bGetEvidence\x12%.attestation_agent.GetEvidenceReque\ - st\x1a&.attestation_agent.GetEvidenceResponse\x12S\n\x08GetToken\x12\".a\ - ttestation_agent.GetTokenRequest\x1a#.attestation_agent.GetTokenResponse\ - \x12\x83\x01\n\x18ExtendRuntimeMeasurement\x122.attestation_agent.Extend\ - RuntimeMeasurementRequest\x1a3.attestation_agent.ExtendRuntimeMeasuremen\ - tResponseb\x06proto3\ + x\"\"\n\x20ExtendRuntimeMeasurementResponse\"K\n\x11InitDataPlaintext\ + \x12\x18\n\x07Content\x18\x01\x20\x01(\x0cR\x07Content\x12\x1c\n\tAlgori\ + thm\x18\x02\x20\x01(\tR\tAlgorithm\".\n\x14CheckInitDataRequest\x12\x16\ + \n\x06Digest\x18\x01\x20\x01(\x0cR\x06Digest\"\x17\n\x15CheckInitDataRes\ + ponse2\xb6\x03\n\x17AttestationAgentService\x12\\\n\x0bGetEvidence\x12%.\ + attestation_agent.GetEvidenceRequest\x1a&.attestation_agent.GetEvidenceR\ + esponse\x12S\n\x08GetToken\x12\".attestation_agent.GetTokenRequest\x1a#.\ + attestation_agent.GetTokenResponse\x12\x83\x01\n\x18ExtendRuntimeMeasure\ + ment\x122.attestation_agent.ExtendRuntimeMeasurementRequest\x1a3.attesta\ + tion_agent.ExtendRuntimeMeasurementResponse\x12b\n\rCheckInitData\x12'.a\ + ttestation_agent.CheckInitDataRequest\x1a(.attestation_agent.CheckInitDa\ + taResponseb\x06proto3\ "; /// `FileDescriptorProto` object which was a source for this generated file @@ -789,13 +1159,16 @@ pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor { file_descriptor.get(|| { let generated_file_descriptor = generated_file_descriptor_lazy.get(|| { let mut deps = ::std::vec::Vec::with_capacity(0); - let mut messages = ::std::vec::Vec::with_capacity(6); + let mut messages = ::std::vec::Vec::with_capacity(9); messages.push(GetEvidenceRequest::generated_message_descriptor_data()); messages.push(GetEvidenceResponse::generated_message_descriptor_data()); messages.push(GetTokenRequest::generated_message_descriptor_data()); messages.push(GetTokenResponse::generated_message_descriptor_data()); messages.push(ExtendRuntimeMeasurementRequest::generated_message_descriptor_data()); messages.push(ExtendRuntimeMeasurementResponse::generated_message_descriptor_data()); + messages.push(InitDataPlaintext::generated_message_descriptor_data()); + messages.push(CheckInitDataRequest::generated_message_descriptor_data()); + messages.push(CheckInitDataResponse::generated_message_descriptor_data()); let mut enums = ::std::vec::Vec::with_capacity(0); ::protobuf::reflect::GeneratedFileDescriptor::new_generated( file_descriptor_proto(), diff --git a/attestation-agent/kbs_protocol/src/token_provider/aa/attestation_agent_ttrpc.rs b/attestation-agent/kbs_protocol/src/token_provider/aa/attestation_agent_ttrpc.rs index 5346928d1..2f917e3c4 100644 --- a/attestation-agent/kbs_protocol/src/token_provider/aa/attestation_agent_ttrpc.rs +++ b/attestation-agent/kbs_protocol/src/token_provider/aa/attestation_agent_ttrpc.rs @@ -46,6 +46,11 @@ impl AttestationAgentServiceClient { let mut cres = super::attestation_agent::ExtendRuntimeMeasurementResponse::new(); ::ttrpc::async_client_request!(self, ctx, req, "attestation_agent.AttestationAgentService", "ExtendRuntimeMeasurement", cres); } + + pub async fn check_init_data(&self, ctx: ttrpc::context::Context, req: &super::attestation_agent::CheckInitDataRequest) -> ::ttrpc::Result { + let mut cres = super::attestation_agent::CheckInitDataResponse::new(); + ::ttrpc::async_client_request!(self, ctx, req, "attestation_agent.AttestationAgentService", "CheckInitData", cres); + } } struct GetEvidenceMethod { @@ -81,6 +86,17 @@ impl ::ttrpc::r#async::MethodHandler for ExtendRuntimeMeasurementMethod { } } +struct CheckInitDataMethod { + service: Arc>, +} + +#[async_trait] +impl ::ttrpc::r#async::MethodHandler for CheckInitDataMethod { + async fn handler(&self, ctx: ::ttrpc::r#async::TtrpcContext, req: ::ttrpc::Request) -> ::ttrpc::Result<::ttrpc::Response> { + ::ttrpc::async_request_handler!(self, ctx, req, attestation_agent, CheckInitDataRequest, check_init_data); + } +} + #[async_trait] pub trait AttestationAgentService: Sync { async fn get_evidence(&self, _ctx: &::ttrpc::r#async::TtrpcContext, _: super::attestation_agent::GetEvidenceRequest) -> ::ttrpc::Result { @@ -92,6 +108,9 @@ pub trait AttestationAgentService: Sync { async fn extend_runtime_measurement(&self, _ctx: &::ttrpc::r#async::TtrpcContext, _: super::attestation_agent::ExtendRuntimeMeasurementRequest) -> ::ttrpc::Result { Err(::ttrpc::Error::RpcStatus(::ttrpc::get_status(::ttrpc::Code::NOT_FOUND, "/attestation_agent.AttestationAgentService/ExtendRuntimeMeasurement is not supported".to_string()))) } + async fn check_init_data(&self, _ctx: &::ttrpc::r#async::TtrpcContext, _: super::attestation_agent::CheckInitDataRequest) -> ::ttrpc::Result { + Err(::ttrpc::Error::RpcStatus(::ttrpc::get_status(::ttrpc::Code::NOT_FOUND, "/attestation_agent.AttestationAgentService/CheckInitData is not supported".to_string()))) + } } pub fn create_attestation_agent_service(service: Arc>) -> HashMap { @@ -108,6 +127,9 @@ pub fn create_attestation_agent_service(service: Arc); + methods.insert("CheckInitData".to_string(), + Box::new(CheckInitDataMethod{service: service.clone()}) as Box); + ret.insert("attestation_agent.AttestationAgentService".to_string(), ::ttrpc::r#async::Service{ methods, streams }); ret } diff --git a/attestation-agent/lib/Cargo.toml b/attestation-agent/lib/Cargo.toml index 3d3f61fd8..f3b5146a0 100644 --- a/attestation-agent/lib/Cargo.toml +++ b/attestation-agent/lib/Cargo.toml @@ -17,6 +17,7 @@ resource_uri.workspace = true reqwest = { workspace = true, features = ["json"], optional = true } serde.workspace = true serde_json.workspace = true +sha2.workspace = true strum.workspace = true thiserror.workspace = true tokio = { workspace = true, features = ["fs"] } diff --git a/attestation-agent/lib/src/lib.rs b/attestation-agent/lib/src/lib.rs index 922c22af3..2acfb4463 100644 --- a/attestation-agent/lib/src/lib.rs +++ b/attestation-agent/lib/src/lib.rs @@ -12,8 +12,7 @@ use async_trait::async_trait; use attester::{detect_tee_type, BoxedAttester}; use kbc::{AnnotationPacket, KbcCheckInfo, KbcInstance, KbcModuleList}; use resource_uri::ResourceUri; -use std::collections::HashMap; -use std::str::FromStr; +use std::{collections::HashMap, str::FromStr}; pub mod config; mod token; @@ -90,6 +89,9 @@ pub trait AttestationAPIs { events: Vec>, register_index: Option, ) -> Result<()>; + + /// Check the initdata binding + async fn check_init_data(&mut self, init_data: &[u8]) -> Result<()>; } /// Attestation agent to provide attestation service. @@ -240,4 +242,13 @@ impl AttestationAPIs for AttestationAgent { .await?; Ok(()) } + + /// Check the initdata binding. If current platform does not support initdata + /// injection, return a success and raise a warning log. + async fn check_init_data(&mut self, init_data: &[u8]) -> Result<()> { + let tee_type = detect_tee_type(); + let attester = TryInto::::try_into(tee_type)?; + attester.check_init_data(init_data).await?; + Ok(()) + } } diff --git a/attestation-agent/protos/attestation-agent.proto b/attestation-agent/protos/attestation-agent.proto index a6a455464..0401fac90 100644 --- a/attestation-agent/protos/attestation-agent.proto +++ b/attestation-agent/protos/attestation-agent.proto @@ -25,8 +25,20 @@ message ExtendRuntimeMeasurementRequest { message ExtendRuntimeMeasurementResponse {} +message InitDataPlaintext { + bytes Content = 1; + string Algorithm = 2; +} + +message CheckInitDataRequest { + bytes Digest = 1; +} + +message CheckInitDataResponse {} + service AttestationAgentService { rpc GetEvidence(GetEvidenceRequest) returns (GetEvidenceResponse) {}; rpc GetToken(GetTokenRequest) returns (GetTokenResponse) {}; rpc ExtendRuntimeMeasurement(ExtendRuntimeMeasurementRequest) returns (ExtendRuntimeMeasurementResponse) {}; + rpc CheckInitData(CheckInitDataRequest) returns (CheckInitDataResponse) {}; } From a5bd93378033c3be8e7689e94e686a9ede82ce0a Mon Sep 17 00:00:00 2001 From: Xynnn007 Date: Mon, 29 Jan 2024 13:55:16 +0800 Subject: [PATCH 2/2] AA/attester: add check_init_data support for TDX and SNP In TDX, we use tdx_attest crate to get a raw hardware tdx report to parse the MRCONFIGID field. In SNP, we use sev crate to get a hardware report to parse HOSTDATA field. The input one should be resize as the evidence field inside the TEE evidence to compare. Signed-off-by: Magnus Kulke Signed-off-by: Dan Mihai Signed-off-by: Xynnn007 --- Cargo.lock | 22 +++++ attestation-agent/attester/Cargo.toml | 4 +- attestation-agent/attester/src/lib.rs | 1 + .../attester/src/snp/hostdata.rs | 24 +++++ attestation-agent/attester/src/snp/mod.rs | 14 +++ attestation-agent/attester/src/tdx/mod.rs | 35 ++++++- attestation-agent/attester/src/tdx/report.rs | 99 +++++++++++++++++++ attestation-agent/attester/src/utils.rs | 15 +++ 8 files changed, 212 insertions(+), 2 deletions(-) create mode 100644 attestation-agent/attester/src/snp/hostdata.rs create mode 100644 attestation-agent/attester/src/tdx/report.rs create mode 100644 attestation-agent/attester/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 9045329e3..b399baad8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -287,11 +287,13 @@ dependencies = [ "log", "nix 0.26.4", "occlum_dcap", + "scroll", "serde", "serde_json", "sev 1.2.1", "strum", "tdx-attest-rs", + "thiserror", "tokio", ] @@ -4806,6 +4808,26 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scroll" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.43", +] + [[package]] name = "scrypt" version = "0.11.0" diff --git a/attestation-agent/attester/Cargo.toml b/attestation-agent/attester/Cargo.toml index 6dd1e98db..e194e8e1d 100644 --- a/attestation-agent/attester/Cargo.toml +++ b/attestation-agent/attester/Cargo.toml @@ -19,6 +19,7 @@ kbs-types.workspace = true log.workspace = true nix = { version = "0.26.2", optional = true } occlum_dcap = { git = "https://github.com/occlum/occlum", tag = "v0.29.7", optional = true } +scroll = { version = "0.11.0", default-features = false, features = ["derive"], optional = true } serde.workspace = true serde_json.workspace = true sev = { version = "1.2.0", default-features = false, features = [ @@ -26,6 +27,7 @@ sev = { version = "1.2.0", default-features = false, features = [ ], optional = true } strum.workspace = true tdx-attest-rs = { git = "https://github.com/intel/SGXDataCenterAttestationPrimitives", tag = "DCAP_1.16", optional = true } +thiserror.workspace = true # TODO: change it to "0.1", once released. csv-rs = { git = "https://github.com/openanolis/csv-rs", rev = "b74aa8c", optional = true } codicon = { version = "3.0", optional = true } @@ -52,7 +54,7 @@ all-attesters = [ "cca-attester", ] -tdx-attester = ["tdx-attest-rs"] +tdx-attester = ["scroll", "tdx-attest-rs"] sgx-attester = ["occlum_dcap"] az-snp-vtpm-attester = ["az-snp-vtpm"] az-tdx-vtpm-attester = ["az-tdx-vtpm"] diff --git a/attestation-agent/attester/src/lib.rs b/attestation-agent/attester/src/lib.rs index 0d47a678d..41e8b2368 100644 --- a/attestation-agent/attester/src/lib.rs +++ b/attestation-agent/attester/src/lib.rs @@ -7,6 +7,7 @@ use anyhow::*; use kbs_types::Tee; pub mod sample; +pub mod utils; #[cfg(feature = "az-snp-vtpm-attester")] pub mod az_snp_vtpm; diff --git a/attestation-agent/attester/src/snp/hostdata.rs b/attestation-agent/attester/src/snp/hostdata.rs new file mode 100644 index 000000000..4dce51ce2 --- /dev/null +++ b/attestation-agent/attester/src/snp/hostdata.rs @@ -0,0 +1,24 @@ +// Copyright (c) 2024 Microsoft Corporation +// Copyright (c) 2024 Alibaba Cloud +// +// SPDX-License-Identifier: Apache-2.0 +// + +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum GetHostDataError { + #[error("Open Sev guest firmware failed: {0}")] + OpenSevGuestFirmware(#[from] std::io::Error), + + #[error("Get report failed: {0}")] + GetReportError(#[from] sev::error::UserApiError), +} + +pub fn get_snp_host_data() -> Result<[u8; 32], GetHostDataError> { + let mut firmware = sev::firmware::guest::Firmware::open()?; + let report_data: [u8; 64] = [0; 64]; + let report = firmware.get_report(None, Some(report_data), Some(0))?; + + Ok(report.host_data) +} diff --git a/attestation-agent/attester/src/snp/mod.rs b/attestation-agent/attester/src/snp/mod.rs index ba950517c..743977f4e 100644 --- a/attestation-agent/attester/src/snp/mod.rs +++ b/attestation-agent/attester/src/snp/mod.rs @@ -3,6 +3,8 @@ // SPDX-License-Identifier: Apache-2.0 // +use crate::utils::pad; + use super::Attester; use anyhow::*; use serde::{Deserialize, Serialize}; @@ -11,6 +13,8 @@ use sev::firmware::guest::Firmware; use sev::firmware::host::CertTableEntry; use std::path::Path; +mod hostdata; + pub fn detect_platform() -> bool { Path::new("/sys/devices/platform/sev-guest").exists() } @@ -47,4 +51,14 @@ impl Attester for SnpAttester { serde_json::to_string(&evidence).context("Serialize SNP evidence failed") } + + async fn check_init_data(&self, init_data: &[u8]) -> Result<()> { + let hostdata = hostdata::get_snp_host_data().context("Get HOSTDATA failed")?; + let init_data: [u8; 32] = pad(init_data); + if init_data != hostdata { + bail!("HOSTDATA does not match."); + } + + Ok(()) + } } diff --git a/attestation-agent/attester/src/tdx/mod.rs b/attestation-agent/attester/src/tdx/mod.rs index aa0d1cf15..78aa619d2 100644 --- a/attestation-agent/attester/src/tdx/mod.rs +++ b/attestation-agent/attester/src/tdx/mod.rs @@ -3,12 +3,18 @@ // SPDX-License-Identifier: Apache-2.0 // +use crate::utils::pad; + use super::Attester; use anyhow::*; use base64::Engine; +use log::debug; +use scroll::Pread; use serde::{Deserialize, Serialize}; use std::path::Path; -use tdx_attest_rs; +use tdx_attest_rs::{self, tdx_report_t}; + +mod report; const TDX_REPORT_DATA_SIZE: usize = 64; const CCEL_PATH: &str = "/sys/firmware/acpi/tables/data/CCEL"; @@ -90,6 +96,33 @@ impl Attester for TdxAttester { Ok(()) } + + async fn check_init_data(&self, init_data: &[u8]) -> Result<()> { + let mut report = tdx_report_t { d: [0; 1024] }; + match tdx_attest_rs::tdx_att_get_report(None, &mut report) { + tdx_attest_rs::tdx_attest_error_t::TDX_ATTEST_SUCCESS => { + debug!("Successfully get report") + } + error_code => { + bail!( + "TDX Attester: Failed to get TD report. Error code: {:?}", + error_code + ); + } + }; + + let td_report = report + .d + .pread::(0) + .map_err(|e| anyhow!("Parse TD report failed: {:?}", e))?; + + let init_data: [u8; 48] = pad(init_data); + if init_data != td_report.tdinfo.mrconfigid { + bail!("Init data does not match!"); + } + + Ok(()) + } } #[cfg(test)] diff --git a/attestation-agent/attester/src/tdx/report.rs b/attestation-agent/attester/src/tdx/report.rs new file mode 100644 index 000000000..5c7a39c52 --- /dev/null +++ b/attestation-agent/attester/src/tdx/report.rs @@ -0,0 +1,99 @@ +// Copyright (c) 2024 Microsoft Corporation +// Copyright (c) 2024 Alibaba Cloud +// +// SPDX-License-Identifier: Apache-2.0 +// + +use scroll::Pread; + +#[repr(C)] +#[derive(Pread)] +/// Type header of TDREPORT_STRUCT. +pub struct TdTransportType { + /// Type of the TDREPORT (0 - SGX, 81 - TDX, rest are reserved). + pub type_: u8, + + /// Subtype of the TDREPORT (Default value is 0). + pub sub_type: u8, + + /// TDREPORT version (Default value is 0). + pub version: u8, + + /// Added for future extension. + pub reserved: u8, +} + +#[repr(C)] +#[derive(Pread)] +/// TDX guest report data, MAC and TEE hashes. +pub struct ReportMac { + /// TDREPORT type header. + pub type_: TdTransportType, + + /// Reserved for future extension. + pub reserved1: [u8; 12], + + /// CPU security version. + pub cpu_svn: [u8; 16], + + /// SHA384 hash of TEE TCB INFO. + pub tee_tcb_info_hash: [u8; 48], + + /// SHA384 hash of TDINFO_STRUCT. + pub tee_td_info_hash: [u8; 48], + + /// User defined unique data passed in TDG.MR.REPORT request. + pub reportdata: [u8; 64], + + /// Reserved for future extension. + pub reserved2: [u8; 32], + + /// CPU MAC ID. + pub mac: [u8; 32], +} + +#[repr(C)] +#[derive(Pread)] +/// TDX guest measurements and configuration. +pub struct TdInfo { + /// TDX Guest attributes (like debug, spet_disable, etc). + pub attr: [u8; 8], + + /// Extended features allowed mask. + pub xfam: u64, + + /// Build time measurement register. + pub mrtd: [u64; 6], + + /// Software-defined ID for non-owner-defined configuration of the guest - e.g., run-time or OS configuration. + pub mrconfigid: [u8; 48], + + /// Software-defined ID for the guest owner. + pub mrowner: [u64; 6], + + /// Software-defined ID for owner-defined configuration of the guest - e.g., specific to the workload. + pub mrownerconfig: [u64; 6], + + /// Run time measurement registers. + pub rtmr: [u64; 24], + + /// For future extension. + pub reserved: [u64; 14], +} + +#[repr(C)] +#[derive(Pread)] +/// Output of TDCALL[TDG.MR.REPORT]. +pub struct TdReport { + /// Mac protected header of size 256 bytes. + pub report_mac: ReportMac, + + /// Additional attestable elements in the TCB are not reflected in the report_mac. + pub tee_tcb_info: [u8; 239], + + /// Added for future extension. + pub reserved: [u8; 17], + + /// Measurements and configuration data of size 512 bytes. + pub tdinfo: TdInfo, +} diff --git a/attestation-agent/attester/src/utils.rs b/attestation-agent/attester/src/utils.rs new file mode 100644 index 000000000..0f7da9ea6 --- /dev/null +++ b/attestation-agent/attester/src/utils.rs @@ -0,0 +1,15 @@ +// Copyright (c) 2024 Microsoft Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +pub fn pad(input: &[u8]) -> [u8; T] { + let mut output = [0; T]; + let len = input.len(); + if len > T { + output.copy_from_slice(&input[..T]); + } else { + output[..len].copy_from_slice(input); + } + output +}