diff --git a/sdk/rust/cctrusted_ccnp/Cargo.toml b/sdk/rust/cctrusted_ccnp/Cargo.toml new file mode 100644 index 0000000..2a0290d --- /dev/null +++ b/sdk/rust/cctrusted_ccnp/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "cctrusted_ccnp" +version = "0.1.0" +edition = "2021" +authors = ["Chen Hairong "] +repository = "https://github.com/cc-api/cc-trusted-api" +description = "CC Trusted API CCNP SDK" +license = "Apache-2.0" + +[lib] +name = "cctrusted_ccnp" +path = "src/lib.rs" + +[dependencies] +cctrusted_base = { git="https://github.com/cc-api/cc-trusted-api" } +anyhow = "1.0" +log = "0.4.20" +tonic = "0.9" +base64 = "0.13.0" +tower = { version = "0.4", features = ["util"] } +prost = "0.11" +tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } +hashbrown = "0.14" +lazy_static = "1.4.0" + +[build-dependencies] +tonic-build = "0.9" \ No newline at end of file diff --git a/sdk/rust/cctrusted_ccnp/build.rs b/sdk/rust/cctrusted_ccnp/build.rs new file mode 100644 index 0000000..edd009c --- /dev/null +++ b/sdk/rust/cctrusted_ccnp/build.rs @@ -0,0 +1,16 @@ +use std::env; +use std::path::PathBuf; + +fn main() -> Result<(), Box> { + tonic_build::compile_protos("proto/ccnp-server.proto")?; + + let original_out_dir = PathBuf::from(env::var("OUT_DIR")?); + let out_dir = "./src"; + + tonic_build::configure() + .out_dir(out_dir) + .file_descriptor_set_path(original_out_dir.join("ccnp_server_descriptor.bin")) + .compile(&["proto/ccnp-server.proto"], &["proto"])?; + + Ok(()) +} diff --git a/sdk/rust/cctrusted_ccnp/deny.toml b/sdk/rust/cctrusted_ccnp/deny.toml new file mode 100644 index 0000000..6e656f7 --- /dev/null +++ b/sdk/rust/cctrusted_ccnp/deny.toml @@ -0,0 +1,35 @@ +[advisories] +vulnerability = "deny" +unmaintained = "warn" +yanked = "warn" +notice = "warn" + +[licenses] +unlicensed = "warn" +allow = [ + "MIT", + "Apache-2.0", + "ISC", + "BSD-3-Clause", + "Unicode-DFS-2016", +] + +copyleft = "warn" +allow-osi-fsf-free = "neither" +default = "deny" +confidence-threshold = 0.8 + +[[licenses.clarify]] +name = "ring" +expression = "MIT AND ISC AND OpenSSL" +license-files = [ + { path = "LICENSE", hash = 0xbd0eed23 } +] + +[bans] +multiple-versions = "warn" +wildcards = "allow" + +[sources] +unknown-registry = "warn" +unknown-git = "warn" \ No newline at end of file diff --git a/sdk/rust/cctrusted_ccnp/proto/ccnp-server.proto b/sdk/rust/cctrusted_ccnp/proto/ccnp-server.proto new file mode 100644 index 0000000..128b56a --- /dev/null +++ b/sdk/rust/cctrusted_ccnp/proto/ccnp-server.proto @@ -0,0 +1,81 @@ +syntax = "proto3"; +package ccnp_server_pb; + +message HealthCheckRequest { + string service = 1; +} + +message HealthCheckResponse { + enum ServingStatus { + UNKNOWN = 0; + SERVING = 1; + NOT_SERVING = 2; + SERVICE_UNKNOWN = 3; + } + ServingStatus status = 1; +} + +service ccnp { + rpc GetDefaultAlgorithm(GetDefaultAlgorithmRequest) returns (GetDefaultAlgorithmResponse); + rpc GetMeasurementCount(GetMeasurementCountRequest) returns (GetMeasurementCountResponse); + rpc GetCcReport (GetCcReportRequest) returns (GetCcReportResponse); + rpc GetCcMeasurement (GetCcMeasurementRequest) returns (GetCcMeasurementResponse) {} + rpc GetCcEventlog (GetCcEventlogRequest) returns (GetCcEventlogResponse) {} +} + +message GetDefaultAlgorithmRequest { +} + +message GetDefaultAlgorithmResponse { + uint32 algo_id = 1; +} + +message GetMeasurementCountRequest { +} + +message GetMeasurementCountResponse { + uint32 count = 1; +} + +message GetCcReportRequest { + string user_data = 1; + string nonce = 2; +} + +message GetCcReportResponse { + uint32 cc_type = 1; + bytes cc_report = 2; +} + +message GetCcMeasurementRequest { + uint32 index = 1; + uint32 algo_id = 2; +} + +message GetCcMeasurementResponse { + TcgDigest measurement = 1; +} + +message GetCcEventlogRequest { + uint32 start = 1; + uint32 count = 2; +} + +message TcgDigest { + uint32 algo_id = 1; + bytes hash = 2; +} + +message TcgEventlog { + uint32 rec_num = 1; + uint32 imr_index = 2; + uint32 event_type = 3; + repeated TcgDigest digests = 4; + uint32 event_size = 5; + bytes event = 6; + map extra_info = 7; +} + +message GetCcEventlogResponse { + repeated TcgEventlog event_logs = 1; +} \ No newline at end of file diff --git a/sdk/rust/cctrusted_ccnp/src/client.rs b/sdk/rust/cctrusted_ccnp/src/client.rs new file mode 100644 index 0000000..63f1fc2 --- /dev/null +++ b/sdk/rust/cctrusted_ccnp/src/client.rs @@ -0,0 +1,175 @@ +use crate::client::ccnp_server_pb::{ + ccnp_client::CcnpClient, GetCcEventlogRequest, GetCcEventlogResponse, GetCcMeasurementRequest, + GetCcMeasurementResponse, GetCcReportRequest, GetCcReportResponse, +}; +use cctrusted_base::api_data::ExtraArgs; +use cctrusted_base::cc_type::TeeType; +use core::result::Result::Ok; +use hashbrown::HashMap; +use tokio::net::UnixStream; +use tonic::transport::{Endpoint, Uri}; +use tonic::Request; +use tower::service_fn; + +//FixMe: use map from cc_type +lazy_static! { + pub static ref TEE_VALUE_TYPE_MAP: HashMap = { + let mut map: HashMap = HashMap::new(); + map.insert(0, TeeType::TPM); + map.insert(1, TeeType::TDX); + map.insert(2, TeeType::SEV); + map.insert(3, TeeType::CCA); + map + }; +} + +pub mod ccnp_server_pb { + tonic::include_proto!("ccnp_server_pb"); + + pub(crate) const FILE_DESCRIPTOR_SET: &[u8] = + tonic::include_file_descriptor_set!("ccnp_server_descriptor"); +} + +pub struct CcnpServiceClient { + pub ccnp_uds_path: String, +} + +impl CcnpServiceClient { + async fn get_cc_report_from_server_async( + &mut self, + nonce: Option, + data: Option, + _extra_args: ExtraArgs, + ) -> Result { + let uds_path = self.ccnp_uds_path.parse::().unwrap(); + let channel = Endpoint::try_from("http://[::]:0") + .unwrap() + .connect_with_connector(service_fn(move |_: Uri| { + UnixStream::connect(uds_path.to_string()) + })) + .await + .unwrap(); + + let request = Request::new(GetCcReportRequest { + nonce: nonce.unwrap(), + user_data: data.unwrap(), + }); + + let mut ccnp_client = CcnpClient::new(channel); + + let response = ccnp_client + .get_cc_report(request) + .await + .unwrap() + .into_inner(); + Ok(response) + } + + // turn async call to sync call + pub fn get_cc_report_from_server( + &mut self, + nonce: Option, + data: Option, + extra_args: ExtraArgs, + ) -> Result { + let response = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap() + .block_on(self.get_cc_report_from_server_async(nonce, data, extra_args)); + response + } + + pub fn get_tee_type_by_value(&self, tee_id: &u32) -> TeeType { + match TEE_VALUE_TYPE_MAP.get(tee_id) { + Some(tee_type) => tee_type.clone(), + None => TeeType::PLAIN, + } + } + + async fn get_cc_measurement_from_server_async( + &mut self, + index: u8, + algo_id: u16, + ) -> Result { + let uds_path = self.ccnp_uds_path.parse::().unwrap(); + let channel = Endpoint::try_from("http://[::]:0") + .unwrap() + .connect_with_connector(service_fn(move |_: Uri| { + UnixStream::connect(uds_path.to_string()) + })) + .await + .unwrap(); + + let request = Request::new(GetCcMeasurementRequest { + index: index.into(), + algo_id: algo_id.into(), + }); + + let mut ccnp_client = CcnpClient::new(channel); + + let response = ccnp_client + .get_cc_measurement(request) + .await + .unwrap() + .into_inner(); + Ok(response) + } + + // turn async call to sync call + pub fn get_cc_measurement_from_server( + &mut self, + index: u8, + algo_id: u16, + ) -> Result { + let response = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap() + .block_on(self.get_cc_measurement_from_server_async(index, algo_id)); + response + } + + async fn get_cc_eventlog_from_server_async( + &mut self, + start: Option, + count: Option, + ) -> Result { + let uds_path = self.ccnp_uds_path.parse::().unwrap(); + let channel = Endpoint::try_from("http://[::]:0") + .unwrap() + .connect_with_connector(service_fn(move |_: Uri| { + UnixStream::connect(uds_path.to_string()) + })) + .await + .unwrap(); + + let request = Request::new(GetCcEventlogRequest { + start: start.unwrap(), + count: count.unwrap(), + }); + + let mut ccnp_client = CcnpClient::new(channel); + + let response = ccnp_client + .get_cc_eventlog(request) + .await + .unwrap() + .into_inner(); + Ok(response) + } + + // turn async call to sync call + pub fn get_cc_eventlog_from_server( + &mut self, + start: Option, + count: Option, + ) -> Result { + let response = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap() + .block_on(self.get_cc_eventlog_from_server_async(start, count)); + response + } +} diff --git a/sdk/rust/cctrusted_ccnp/src/lib.rs b/sdk/rust/cctrusted_ccnp/src/lib.rs new file mode 100644 index 0000000..a81b108 --- /dev/null +++ b/sdk/rust/cctrusted_ccnp/src/lib.rs @@ -0,0 +1,5 @@ +#[macro_use] +extern crate lazy_static; + +pub mod client; +pub mod sdk; diff --git a/sdk/rust/cctrusted_ccnp/src/sdk.rs b/sdk/rust/cctrusted_ccnp/src/sdk.rs new file mode 100644 index 0000000..11a79ac --- /dev/null +++ b/sdk/rust/cctrusted_ccnp/src/sdk.rs @@ -0,0 +1,136 @@ +use crate::client::CcnpServiceClient; +use anyhow::*; +use cctrusted_base::api::CCTrustedApi; +use cctrusted_base::api_data::ReplayResult; +use cctrusted_base::api_data::{Algorithm, CcReport, ExtraArgs}; +use cctrusted_base::binary_blob::dump_data; +use cctrusted_base::eventlog::EventLogs; +use cctrusted_base::tcg::*; +use core::result::Result::Ok; + +const UDS_PATH: &str = "/run/ccnp/uds/ccnp-server.sock"; + +pub struct API {} + +impl CCTrustedApi for API { + // CCTrustedApi trait function: get cc report from CCNP server + fn get_cc_report( + nonce: Option, + data: Option, + extra_args: ExtraArgs, + ) -> Result { + let mut ccnp_service_client = CcnpServiceClient { + ccnp_uds_path: UDS_PATH.to_string(), + }; + + let response = match ccnp_service_client.get_cc_report_from_server(nonce, data, extra_args) + { + Ok(r) => r, + Err(e) => { + return Err(anyhow!("[get_cc_report] err get cc report: {:?}", e)); + } + }; + + Ok(CcReport { + cc_report: response.cc_report, + cc_type: ccnp_service_client.get_tee_type_by_value(&response.cc_type), + }) + } + + // CCTrustedApi trait function: dump report of a CVM in hex and char format + fn dump_cc_report(report: &Vec) { + dump_data(report) + } + + // CCTrustedApi trait function: get max number of CVM IMRs + fn get_measurement_count() -> Result { + todo!() + } + + // CCTrustedApi trait function: get measurements of a CVM + fn get_cc_measurement(index: u8, algo_id: u16) -> Result { + let mut ccnp_service_client = CcnpServiceClient { + ccnp_uds_path: UDS_PATH.to_string(), + }; + + let response = match ccnp_service_client.get_cc_measurement_from_server(index, algo_id) { + Ok(r) => r, + Err(e) => { + return Err(anyhow!( + "[get_cc_measurement] err get cc measurement: {:?}", + e + )); + } + }; + + let measurement = match response.measurement { + Some(measurement) => measurement, + None => return Err(anyhow!("[get_cc_measurement] faile to get cc measurement")), + }; + + Ok(TcgDigest { + algo_id: measurement.algo_id as u16, + hash: measurement.hash, + }) + } + + // CCTrustedApi trait function: get eventlogs of a CVM + fn get_cc_eventlog( + start: Option, + count: Option, + ) -> Result, anyhow::Error> { + let mut ccnp_service_client = CcnpServiceClient { + ccnp_uds_path: UDS_PATH.to_string(), + }; + + let response = match ccnp_service_client.get_cc_eventlog_from_server(start, count) { + Ok(r) => r, + Err(e) => { + return Err(anyhow!("[get_cc_eventlog] err get cc eventlog: {:?}", e)); + } + }; + + let mut event_logs: Vec = Vec::new(); + + for el in response.event_logs { + if !el.digests.is_empty() { + let mut digests: Vec = Vec::new(); + for d in el.digests { + digests.push(TcgDigest { + algo_id: d.algo_id as u16, + hash: d.hash, + }); + } + event_logs.push(EventLogEntry::TcgImrEvent(TcgImrEvent { + imr_index: el.imr_index, + event_type: el.event_type, + digests: digests.clone(), + event_size: el.event_size, + event: el.event.clone(), + })); + } else { + event_logs.push(EventLogEntry::TcgPcClientImrEvent(TcgPcClientImrEvent { + imr_index: el.imr_index, + event_type: el.event_type, + digest: el.digests[0].hash[0..20].try_into().unwrap(), + event_size: el.event_size, + event: el.event.clone(), + })); + } + } + + Ok(event_logs) + } + + // CCTrustedApi trait function: replay eventlogs of a CVM + fn replay_cc_eventlog( + eventlogs: Vec, + ) -> Result, anyhow::Error> { + EventLogs::replay(eventlogs) + } + + // CCTrustedApi trait function: get default algorithm of a CVM + fn get_default_algorithm() -> Result { + todo!() + } +} diff --git a/sdk/rust/sample/Cargo.toml b/sdk/rust/sample/Cargo.toml new file mode 100644 index 0000000..37e1efb --- /dev/null +++ b/sdk/rust/sample/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "cctrusted-ccnp-sample" +version = "0.1.0" +edition = "2021" +license = "Apache-2.0" + +[[bin]] +name = "cc-sample-quote" +path = "src/cc-sample-quote.rs" + +[[bin]] +name = "cc-sample-measurement" +path = "src/cc-sample-measurement.rs" + +[[bin]] +name = "cc-sample-eventlog" +path = "src/cc-sample-eventlog.rs" + +[dependencies] +cctrusted_ccnp = { path = "../cctrusted_ccnp" } +cctrusted_base = { git="https://github.com/cc-api/cc-trusted-api" } +anyhow = "1.0" +log = "0.4.20" +env_logger = "0.10.1" +base64 = "0.13.0" +rand = "0.8.5" +tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } diff --git a/sdk/rust/sample/src/cc-sample-eventlog.rs b/sdk/rust/sample/src/cc-sample-eventlog.rs new file mode 100644 index 0000000..19a8f6a --- /dev/null +++ b/sdk/rust/sample/src/cc-sample-eventlog.rs @@ -0,0 +1,61 @@ +use cctrusted_base::api::*; +use cctrusted_base::tcg::EventLogEntry; +use cctrusted_ccnp::sdk::API; +use log::*; + +fn main() { + // set log level + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); + + // retrieve cc eventlog with API "get_cc_eventlog" + let eventlogs = match API::get_cc_eventlog(Some(0), Some(101)) { + Ok(q) => q, + Err(e) => { + error!("error getting eventlog: {:?}", e); + return; + } + }; + + info!("event log count: {}", eventlogs.len()); + // for eventlog in &eventlogs { + // eventlog.show(); + // } + + // retrieve cc eventlog in batch + // let mut eventlogs1: Vec = Vec::new(); + // let mut start = 0; + // let batch_size = 10; + // loop { + // let event_logs = match API::get_cc_eventlog(Some(start), Some(batch_size)) { + // Ok(q) => q, + // Err(e) => { + // error!("error get eventlog: {:?}", e); + // return; + // } + // }; + // for event_log in &event_logs { + // eventlogs1.push(event_log.clone()); + // } + // if !event_logs.is_empty() { + // start += event_logs.len() as u32; + // } else { + // break; + // } + // } + + //info!("event log count: {}", eventlogs1.len()); + + // replay cc eventlog with API "replay_cc_eventlog" + let replay_results = match API::replay_cc_eventlog(eventlogs) { + Ok(q) => q, + Err(e) => { + error!("error replay eventlog: {:?}", e); + return; + } + }; + + // show replay results + for replay_result in replay_results { + replay_result.show(); + } +} diff --git a/sdk/rust/sample/src/cc-sample-measurement.rs b/sdk/rust/sample/src/cc-sample-measurement.rs new file mode 100644 index 0000000..8be2948 --- /dev/null +++ b/sdk/rust/sample/src/cc-sample-measurement.rs @@ -0,0 +1,54 @@ +use cctrusted_base::api::*; +use cctrusted_base::tcg::TcgAlgorithmRegistry; +use cctrusted_ccnp::sdk::API; + +use log::*; + +fn main() { + // set log level + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); + + // get CVM default algorithm with API "get_default_algorithm" + // info!("call cc trusted API [get_default_algorithm] to get CVM supported algorithm!"); + // let defalt_algo = match API::get_default_algorithm() { + // Ok(algorithm) => { + // info!("supported algorithm: {}", algorithm.algo_id_str); + // algorithm + // } + // Err(e) => { + // error!("error get algorithm: {:?}", e); + // return; + // } + // }; + + // get number of measurement registers in CVM + // info!("call cc trusted API [get_measurement_count] to get number of measurement registers in CVM!"); + // let count = match API::get_measurement_count() { + // Ok(count) => { + // info!("measurement registers count: {}", count); + // count + // } + // Err(e) => { + // error!("error get measurement count: {:?}", e); + // return; + // } + // }; + + // retrive and show measurement registers in CVM + info!("call cc trusted API [get_cc_measurement] to get measurement register content in CVM!"); + for index in 0..4 { + let tcg_digest = match API::get_cc_measurement(index, 12) { + Ok(tcg_digest) => tcg_digest, + Err(e) => { + error!("error get measurement: {:?}", e); + return; + } + }; + info!( + "show index = {}, algo = {:?}, hash = {:02X?}", + index, + tcg_digest.get_algorithm_id_str(), + tcg_digest.get_hash() + ); + } +} diff --git a/sdk/rust/sample/src/cc-sample-quote.rs b/sdk/rust/sample/src/cc-sample-quote.rs new file mode 100644 index 0000000..5bd9f6d --- /dev/null +++ b/sdk/rust/sample/src/cc-sample-quote.rs @@ -0,0 +1,56 @@ +use cctrusted_base::api::*; +use cctrusted_base::api_data::*; +use cctrusted_base::cc_type::TeeType; +use cctrusted_base::tdx::quote::TdxQuote; +use cctrusted_ccnp::sdk::API; +use log::*; +use rand::Rng; + +fn main() { + // set log level + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); + + /*** + * Note: in real user case, the nonce should come from attestation server + * side to prevent replay attack and the data should be generate by API caller + * according to user define spec + */ + let nonce = base64::encode(rand::thread_rng().gen::<[u8; 32]>()); + let data = base64::encode(rand::thread_rng().gen::<[u8; 32]>()); + + // retrieve cc report with API "get_cc_report" + info!("call cc trusted API [get_cc_report] to retrieve cc report!"); + let report = match API::get_cc_report(Some(nonce), Some(data), ExtraArgs {}) { + Ok(q) => q, + Err(e) => { + info!("error getting cc report: {:?}", e); + return; + } + }; + + info!("length of the cc report: {}", report.cc_report.len()); + + // dump the cc report with API "dump_cc_report" + //info!("call cc trusted API [dump_cc_report] to dump cc report!"); + //API::dump_cc_report(&report.cc_report); + + // parse the cc report with API "parse_cc_report" + if report.cc_type == TeeType::TDX { + let tdx_quote: TdxQuote = match CcReport::parse_cc_report(report.cc_report) { + Ok(q) => q, + Err(e) => { + info!("error parse tdx quote: {:?}", e); + return; + } + }; + info!( + "version = {}, report_data = {}", + tdx_quote.header.version, + base64::encode(tdx_quote.body.report_data) + ); + + // show data of the struct TdxQuoteHeader + info!("call struct show function to show data of the struct TdxQuoteHeader!"); + tdx_quote.header.show(); + } +}