From f753fe5bee5eed771f6dc70aebd0b9e2cf412077 Mon Sep 17 00:00:00 2001 From: Xynnn007 Date: Fri, 24 Nov 2023 16:23:39 +0800 Subject: [PATCH 1/6] AS: Change the API of evaluate function of AS Before this commit, the API of Attestation-Service lib is coupled with KBS protocol. Also, not all functionalities of verifier driver are used, including checking the binding of initdata hash and runtime data hash. This commit will help to only have attestation related parameters in this API. Signed-off-by: Xynnn007 --- Cargo.lock | 1 + Cargo.toml | 1 + .../attestation-service/Cargo.toml | 2 + .../attestation-service/src/lib.rs | 196 ++++++++++++++---- .../src/policy_engine/mod.rs | 13 +- .../src/policy_engine/opa/mod.rs | 125 ++++++----- attestation-service/verifier/Cargo.toml | 2 +- 7 files changed, 253 insertions(+), 87 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 06d34d144..5448b24c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -531,6 +531,7 @@ dependencies = [ "base64 0.21.5", "cfg-if", "futures", + "hex", "kbs-types", "lazy_static", "log", diff --git a/Cargo.toml b/Cargo.toml index b4cc5fe98..492be0f51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ cfg-if = "1.0.0" chrono = "0.4.19" clap = { version = "4", features = ["derive"] } env_logger = "0.10.0" +hex = "0.4.3" kbs-types.path = "attestation-service/as-types" log = "0.4.17" prost = "0.11.0" diff --git a/attestation-service/attestation-service/Cargo.toml b/attestation-service/attestation-service/Cargo.toml index 026e8c16a..12eb1b03e 100644 --- a/attestation-service/attestation-service/Cargo.toml +++ b/attestation-service/attestation-service/Cargo.toml @@ -24,6 +24,7 @@ async-trait.workspace = true base64 = "0.21" cfg-if = "1.0.0" futures = "0.3.17" +hex.workspace = true # TODO: change it to "0.5", once released. kbs-types = { git = "https://github.com/virtee/kbs-types", rev = "c90df0e" } lazy_static = "1.4.0" @@ -51,6 +52,7 @@ tonic-build.workspace = true [dev-dependencies] assert-json-diff.workspace = true +hex.workspace = true rstest.workspace = true serial_test.workspace = true sha2.workspace = true diff --git a/attestation-service/attestation-service/src/lib.rs b/attestation-service/attestation-service/src/lib.rs index a94cf2b8f..e845957f7 100644 --- a/attestation-service/attestation-service/src/lib.rs +++ b/attestation-service/attestation-service/src/lib.rs @@ -2,7 +2,7 @@ //! //! # Features //! - `rvps-grpc`: The AS will connect a remote RVPS. -//! - `rvps-native`: The AS will integrate RVPS functionalities itself. +//! - `rvps-builtin`: The AS will integrate RVPS functionalities itself. pub mod config; pub mod policy_engine; @@ -18,14 +18,64 @@ pub use kbs_types::{Attestation, Tee}; use log::debug; use policy_engine::{PolicyEngine, PolicyEngineType, SetPolicyInput}; use rvps::RvpsApi; -use serde_json::json; -use sha2::{Digest, Sha384}; +use serde_json::{json, Value}; +use sha2::{Digest, Sha256, Sha384, Sha512}; use std::{collections::HashMap, str::FromStr}; +use strum::{AsRefStr, EnumString}; use tokio::fs; use verifier::{InitDataHash, ReportData}; use crate::utils::flatten_claims; +/// Hash algorithms used to calculate runtime/init data binding +#[derive(EnumString, AsRefStr)] +pub enum HashAlgorithm { + #[strum(ascii_case_insensitive)] + Sha256, + + #[strum(ascii_case_insensitive)] + Sha384, + + #[strum(ascii_case_insensitive)] + Sha512, +} + +impl HashAlgorithm { + fn accumulate_hash(&self, materials: Vec) -> Vec { + match self { + HashAlgorithm::Sha256 => { + let mut hasher = Sha256::new(); + hasher.update(materials); + hasher.finalize().to_vec() + } + HashAlgorithm::Sha384 => { + let mut hasher = Sha384::new(); + hasher.update(materials); + hasher.finalize().to_vec() + } + HashAlgorithm::Sha512 => { + let mut hasher = Sha512::new(); + hasher.update(materials); + hasher.finalize().to_vec() + } + } + } +} + +/// Runtime/Init Data used to check the binding relationship with report data +/// in Evidence +pub enum Data { + /// This will be used as the expected runtime/init data to check against + /// the one inside evidence. + Raw(Vec), + + /// Runtime/Init data in a JSON map. CoCoAS will rearrange each layer of the + /// data JSON object in dictionary order by key, then serialize and output + /// it into a compact string, and perform hash calculation on the whole + /// to check against the one inside evidence. + Structured(Value), +} + pub struct AttestationService { _config: Config, policy_engine: Box, @@ -72,44 +122,53 @@ impl AttestationService { .map_err(|e| anyhow!("Cannot Set Policy: {:?}", e)) } - fn accumulate_hash(materials: &[Vec]) -> Option> { - if materials.is_empty() { - return None; - } - let mut hasher = Sha384::new(); - materials.iter().for_each(|m| hasher.update(m)); - Some(hasher.finalize().to_vec()) - } - /// Evaluate Attestation Evidence. - /// Issue an attestation results token which contain TCB status and TEE public key. - pub async fn evaluate(&self, tee: Tee, nonce: &str, attestation: &str) -> Result { - let attestation = serde_json::from_str::(attestation) - .context("Failed to deserialize Attestation")?; + /// Issue an attestation results token which contain TCB status and TEE public key. Input parameters: + /// - `evidence`: TEE evidence bytes. This might not be the raw hardware evidence bytes. Definitions + /// are in `verifier` crate. + /// - `tee`: concrete TEE type + /// - `runtime_data`: These data field will be used to check against the counterpart inside the evidence. + /// The concrete way of checking is decide by the enum type. If this parameter is set `None`, the comparation + /// will not be performed. + /// - `init_data`: These data field will be used to check against the counterpart inside the evidence. + /// The concrete way of checking is decide by the enum type. If this parameter is set `None`, the comparation + /// will not be performed. + /// - `hash_algorithm`: The hash algorithm that is used to calculate the digest of `runtime_data` and + /// `init_data`. + /// - `policy_ids`: The policy ids that used to check this evidence. Any check fails against a policy will + /// not cause this function to return error. The result check against every policy will be included inside + /// the finally Token returned by CoCo-AS. + #[allow(clippy::too_many_arguments)] + pub async fn evaluate( + &self, + evidence: Vec, + tee: Tee, + runtime_data: Option, + runtime_data_hash_algorithm: HashAlgorithm, + init_data: Option, + init_data_hash_algorithm: HashAlgorithm, + policy_ids: Vec, + ) -> Result { let verifier = verifier::to_verifier(&tee)?; - let report_data = Self::accumulate_hash(&[ - nonce.as_bytes().to_vec(), - attestation.tee_pubkey.k_mod.as_bytes().to_vec(), - attestation.tee_pubkey.k_exp.as_bytes().to_vec(), - ]); + let (report_data, runtime_data_claims) = + parse_data(runtime_data, &runtime_data_hash_algorithm).context("parse runtime data")?; let report_data = match &report_data { - Some(value) => ReportData::Value(value), + Some(data) => ReportData::Value(data), None => ReportData::NotProvided, }; + let (init_data, init_data_claims) = + parse_data(init_data, &init_data_hash_algorithm).context("parse init data")?; + + let init_data_hash = match &init_data { + Some(data) => InitDataHash::Value(data), + None => InitDataHash::NotProvided, + }; + let claims_from_tee_evidence = verifier - .evaluate( - attestation.tee_evidence.as_bytes(), - &report_data, - // We currently do not need to check the initdata hash in AS when using - // `verifier` crate. - // - // We will refactor the CoCo-AS' API to leverage the parameter. - // See https://github.com/confidential-containers/kbs/issues/177 - &InitDataHash::NotProvided, - ) + .evaluate(&evidence, &report_data, &init_data_hash) .await .map_err(|e| anyhow!("Verifier evaluate failed: {e:?}"))?; @@ -122,18 +181,38 @@ impl AttestationService { .await .map_err(|e| anyhow!("Generate reference data failed{:?}", e))?; - // Now only support using default policy to evaluate let evaluation_report = self .policy_engine - .evaluate(reference_data_map, tcb_json, None) + .evaluate(reference_data_map.clone(), tcb_json, policy_ids.clone()) .await .map_err(|e| anyhow!("Policy Engine evaluation failed: {e}"))?; + let policies: Vec<_> = evaluation_report + .into_iter() + .map(|(k, v)| { + json!({ + "policy-id": k, + "policy-hash": v.0, + "evaluation-result": v.1, + }) + }) + .collect(); + + let reference_data_map: HashMap> = reference_data_map + .into_iter() + .filter(|it| !it.1.is_empty()) + .collect(); + let token_claims = json!({ - "tee-pubkey": attestation.tee_pubkey.clone(), + "evaluation-reports": policies, "tcb-status": flattened_claims, - "evaluation-report": evaluation_report, + "reference-data": reference_data_map, + "customized_claims": { + "init_data": init_data_claims, + "runtime_data": runtime_data_claims, + }, }); + let attestation_results_token = self.token_broker.issue(token_claims)?; Ok(attestation_results_token) @@ -159,3 +238,48 @@ impl AttestationService { self.rvps.verify_and_extract(message).await } } + +/// Get the expected init/runtime data and potential claims due to the given input +/// and the hash algorithm +fn parse_data( + data: Option, + hash_algorithm: &HashAlgorithm, +) -> Result<(Option>, Value)> { + match data { + Some(value) => match value { + Data::Raw(raw) => Ok((Some(raw), Value::Null)), + Data::Structured(structured) => { + // by default serde_json will enforence the alphabet order for keys + let hash_materials = + serde_json::to_vec(&structured).context("parse JSON structured data")?; + let digest = hash_algorithm.accumulate_hash(hash_materials); + Ok((Some(digest), structured)) + } + }, + None => Ok((None, Value::Null)), + } +} + +#[cfg(test)] +mod tests { + use assert_json_diff::assert_json_eq; + use rstest::rstest; + use serde_json::{json, Value}; + + use crate::{Data, HashAlgorithm}; + + #[rstest] + #[case(Some(Data::Raw(b"aaaaa".to_vec())), Some(b"aaaaa".to_vec()), HashAlgorithm::Sha384, Value::Null)] + #[case(None, None, HashAlgorithm::Sha384, Value::Null)] + #[case(Some(Data::Structured(json!({"b": 1, "a": "test", "c": {"d": "e"}}))), Some(hex::decode(b"e71ce8e70d814ba6639c3612ebee0ff1f76f650f8dbb5e47157e0f3f525cd22c4597480a186427c813ca941da78870c3").unwrap()), HashAlgorithm::Sha384, json!({"b": 1, "a": "test", "c": {"d": "e"}}))] + fn parse_data_json_binding( + #[case] input: Option, + #[case] expected_data: Option>, + #[case] hash_algorithm: HashAlgorithm, + #[case] expected_claims: Value, + ) { + let (data, data_claims) = crate::parse_data(input, &hash_algorithm).expect("parse failed"); + assert_eq!(data, expected_data); + assert_json_eq!(data_claims, expected_claims); + } +} diff --git a/attestation-service/attestation-service/src/policy_engine/mod.rs b/attestation-service/attestation-service/src/policy_engine/mod.rs index ea36c0d4d..6f4f919ef 100644 --- a/attestation-service/attestation-service/src/policy_engine/mod.rs +++ b/attestation-service/attestation-service/src/policy_engine/mod.rs @@ -27,7 +27,6 @@ pub enum PolicyType { } impl PolicyEngineType { - #[allow(dead_code)] pub fn to_policy_engine(&self, work_dir: &Path) -> Result> { match self { PolicyEngineType::OPA => Ok(Box::new(opa::OPA::new(work_dir.to_path_buf())?) @@ -36,14 +35,22 @@ impl PolicyEngineType { } } +type PolicyDigest = String; +type EvaluationResult = serde_json::Value; + #[async_trait] pub trait PolicyEngine { + /// The result is a key-value map. + /// - `key`: the policy id + /// - `value`: It will be a tuple. The first element is the digest of + /// the policy (using **Sha384**). The second element is the evaluation + /// output of the policy. async fn evaluate( &self, reference_data_map: HashMap>, input: String, - policy_id: Option, - ) -> Result; + policy_ids: Vec, + ) -> Result>; async fn set_policy(&mut self, input: SetPolicyInput) -> Result<()>; } diff --git a/attestation-service/attestation-service/src/policy_engine/opa/mod.rs b/attestation-service/attestation-service/src/policy_engine/opa/mod.rs index 49b50c9e0..26e1bfe33 100644 --- a/attestation-service/attestation-service/src/policy_engine/opa/mod.rs +++ b/attestation-service/attestation-service/src/policy_engine/opa/mod.rs @@ -1,5 +1,5 @@ use crate::policy_engine::{PolicyEngine, PolicyType}; -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, bail, Context, Result}; use async_trait::async_trait; use base64::Engine; use log::debug; @@ -11,7 +11,7 @@ use std::os::raw::c_char; use std::path::PathBuf; use std::str::FromStr; -use super::SetPolicyInput; +use super::{EvaluationResult, PolicyDigest, SetPolicyInput}; // Link import cgo function #[link(name = "cgo")] @@ -63,51 +63,76 @@ impl PolicyEngine for OPA { &self, reference_data_map: HashMap>, input: String, - policy_id: Option, - ) -> Result { - let policy_file_path = format!( - "{}/{}.rego", - self.policy_dir_path - .to_str() - .ok_or_else(|| anyhow!("Miss Policy DirPath"))?, - policy_id.unwrap_or("default".to_string()) - ); - let policy = tokio::fs::read_to_string(policy_file_path) - .await - .map_err(|e| anyhow!("Read OPA policy file failed: {:?}", e))?; - - let policy_go = GoString { - p: policy.as_ptr() as *const c_char, - n: policy.len() as isize, - }; - - let reference = serde_json::json!({ "reference": reference_data_map }).to_string(); - - let reference_go = GoString { - p: reference.as_ptr() as *const c_char, - n: reference.len() as isize, - }; + policy_ids: Vec, + ) -> Result> { + let mut res = HashMap::new(); + + let policy_dir_path = self + .policy_dir_path + .to_str() + .ok_or_else(|| anyhow!("Miss Policy DirPath"))?; + + for policy_id in policy_ids { + let policy_file_path = format!("{policy_dir_path}/{policy_id}.rego"); + + let policy = tokio::fs::read_to_string(policy_file_path) + .await + .map_err(|e| anyhow!("Read OPA policy file failed: {:?}", e))?; + + let policy_hash = { + use sha2::Digest; + let mut hasher = sha2::Sha384::new(); + hasher.update(&policy); + let hex = hasher.finalize().to_vec(); + hex::encode(hex) + }; + + let policy_go = GoString { + p: policy.as_ptr() as *const c_char, + n: policy.len() as isize, + }; + + let reference = serde_json::json!({ "reference": reference_data_map }).to_string(); + + let reference_go = GoString { + p: reference.as_ptr() as *const c_char, + n: reference.len() as isize, + }; + + let input_go = GoString { + p: input.as_ptr() as *const c_char, + n: input.len() as isize, + }; + + // Call the function exported by cgo and process the returned decision + let decision_buf: *mut c_char = + unsafe { evaluateGo(policy_go, reference_go, input_go) }; + let decision_str: &CStr = unsafe { CStr::from_ptr(decision_buf) }; + let policy_res = decision_str.to_str()?.to_string(); + debug!("Evaluated: {}", policy_res); + if policy_res.starts_with("Error::") { + bail!("OPA verification failed: {policy_res}"); + } - let input_go = GoString { - p: input.as_ptr() as *const c_char, - n: input.len() as isize, - }; + // If a clear approval opinion is given in the evaluation report, + // the rejection information will be reflected in the evaluation failure return value. + let res_kv: Value = serde_json::from_str(&policy_res)?; - // Call the function exported by cgo and process the returned decision - let decision_buf: *mut c_char = unsafe { evaluateGo(policy_go, reference_go, input_go) }; - let decision_str: &CStr = unsafe { CStr::from_ptr(decision_buf) }; - let res = decision_str.to_str()?.to_string(); - debug!("Evaluated: {}", res); - if res.starts_with("Error::") { - return Err(anyhow!(res)); - } + // only if there is a field named `allow` in the evaluation report and + // it is false, the evaluation fails. Otherwise the evaluation will be + // treated as succees. + let allow = res_kv + .get("allow") + .map(|a| a.as_bool()) + .map(|a| a.unwrap_or(true)) + .unwrap_or(true); - // If a clear approval opinion is given in the evaluation report, - // the rejection information will be reflected in the evaluation failure return value. - let res_kv: Value = serde_json::from_str(&res)?; - if let Some(allow) = res_kv["allow"].as_bool() { if !allow { - bail!("Untrusted TEE evidence") + bail!("TEE evidence does not pass policy {policy_id}, reason: {policy_res}"); + } else { + let evaluation_result = + serde_json::from_str(&policy_res).context("serialize OPA result")?; + res.insert(policy_id.to_owned(), (policy_hash, evaluation_result)); } } @@ -141,6 +166,7 @@ impl PolicyEngine for OPA { #[cfg(test)] mod tests { use super::*; + use assert_json_diff::assert_json_eq; use serde_json::json; fn dummy_reference(ver: u64) -> String { @@ -173,15 +199,20 @@ mod tests { .evaluate( reference_data.clone(), dummy_input(5, 5), - Some(default_policy_id.clone()), + vec![default_policy_id.clone()], ) .await; - assert!(res.is_ok(), "OPA execution() should be success"); + let res = res.expect("OPA execution should succeed"); + // this expected value is calculated by `sha384sum` + let expected_digest = "c0e7929671fb6780387f54760d84d65d2ce96093dfb33efda21f5eb05afcda77bba444c02cd177b23a5d350716726157"; + assert_eq!(expected_digest, res["default_policy"].0); + assert_json_eq!(json!({"allow":true}), res["default_policy"].1); let res = opa - .evaluate(reference_data, dummy_input(0, 0), Some(default_policy_id)) + .evaluate(reference_data, dummy_input(0, 0), vec![default_policy_id]) .await; - assert!(res.is_err(), "OPA execution() should be failed"); + + res.expect_err("OPA execution should fail"); } #[tokio::test] diff --git a/attestation-service/verifier/Cargo.toml b/attestation-service/verifier/Cargo.toml index 2db8430c0..f27a4ef80 100644 --- a/attestation-service/verifier/Cargo.toml +++ b/attestation-service/verifier/Cargo.toml @@ -26,7 +26,7 @@ codicon = { version = "3.0", optional = true } # TODO: change it to "0.1", once released. csv-rs = { git = "https://gitee.com/anolis/csv-rs", rev = "9d8882e", optional = true } eventlog-rs = { version = "0.1.3", optional = true } -hex = "0.4.3" +hex.workspace = true jsonwebtoken = "8" # TODO: change it to "0.5", once released. kbs-types = { git = "https://github.com/virtee/kbs-types", rev = "c90df0e" } From 1de7f6985f6a57378a7f6872032f4aaac8d838de Mon Sep 17 00:00:00 2001 From: Xynnn007 Date: Fri, 24 Nov 2023 17:26:27 +0800 Subject: [PATCH 2/6] AS/proto: add CCA enum to TEE Signed-off-by: Xynnn007 --- attestation-service/protos/attestation.proto | 1 + 1 file changed, 1 insertion(+) diff --git a/attestation-service/protos/attestation.proto b/attestation-service/protos/attestation.proto index 6eb5d0c4b..dc38d56f6 100644 --- a/attestation-service/protos/attestation.proto +++ b/attestation-service/protos/attestation.proto @@ -10,6 +10,7 @@ enum Tee { Sample = 4; AzSnpVtpm = 5; CSV = 6; + CCA = 7; } message AttestationRequest { From 0162a8e05651e1c697ca9e944c5b0f2cdc382f62 Mon Sep 17 00:00:00 2001 From: Xynnn007 Date: Fri, 24 Nov 2023 16:49:18 +0800 Subject: [PATCH 3/6] AS/gRPC: Update the API After update the API of attestation-service lib, gRPC service also needs to be updated. Before this commit, the API of gRPC AS is coupled with KBS protocol. It is needed for a user to just use CoCo-AS to do some verification work without coperating with KBS. Signed-off-by: Xynnn007 --- Cargo.lock | 1 + attestation-service/bin/grpc-as/Cargo.toml | 1 + attestation-service/bin/grpc-as/src/server.rs | 93 +++++++++++++++++-- attestation-service/protos/attestation.proto | 64 ++++++++++++- 4 files changed, 150 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5448b24c1..1a0adccb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1910,6 +1910,7 @@ dependencies = [ "anyhow", "async-trait", "attestation-service", + "base64 0.21.5", "clap 4.4.10", "env_logger 0.10.1", "log", diff --git a/attestation-service/bin/grpc-as/Cargo.toml b/attestation-service/bin/grpc-as/Cargo.toml index 01978b179..179e23871 100644 --- a/attestation-service/bin/grpc-as/Cargo.toml +++ b/attestation-service/bin/grpc-as/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" anyhow.workspace = true async-trait.workspace = true attestation-service = { path = "../../attestation-service", features = ["rvps-grpc"] } +base64.workspace = true clap.workspace = true env_logger.workspace = true log.workspace = true diff --git a/attestation-service/bin/grpc-as/src/server.rs b/attestation-service/bin/grpc-as/src/server.rs index 88e9242f0..7a46235a9 100644 --- a/attestation-service/bin/grpc-as/src/server.rs +++ b/attestation-service/bin/grpc-as/src/server.rs @@ -1,6 +1,9 @@ use anyhow::{anyhow, Result}; use attestation_service::policy_engine::SetPolicyInput; +use attestation_service::HashAlgorithm; use attestation_service::{config::Config, AttestationService as Service, Tee}; +use base64::engine::general_purpose::URL_SAFE_NO_PAD; +use base64::Engine; use log::{debug, info}; use std::net::SocketAddr; use std::path::Path; @@ -32,6 +35,7 @@ fn to_kbs_tee(tee: GrpcTee) -> Tee { GrpcTee::Csv => Tee::Csv, GrpcTee::Sample => Tee::Sample, GrpcTee::AzSnpVtpm => Tee::AzSnpVtpm, + GrpcTee::Cca => Tee::Cca, } } @@ -86,20 +90,95 @@ impl AttestationService for Arc> { debug!("Evidence: {}", &request.evidence); + let tee = to_kbs_tee( + GrpcTee::from_i32(request.tee) + .ok_or_else(|| Status::aborted(format!("Invalid TEE {}", request.tee)))?, + ); + let evidence = URL_SAFE_NO_PAD + .decode(request.evidence) + .map_err(|e| Status::aborted(format!("Illegal input Evidence: {e}")))?; + + let runtime_data = match request.runtime_data { + Some(runtime_data) => match runtime_data { + crate::as_api::attestation_request::RuntimeData::RawRuntimeData(raw) => { + let raw_runtime = URL_SAFE_NO_PAD + .decode(raw) + .map_err(|e| Status::aborted(format!("base64 decode runtime data: {e}")))?; + Some(attestation_service::Data::Raw(raw_runtime)) + } + crate::as_api::attestation_request::RuntimeData::StructuredRuntimeData( + structured, + ) => { + let structured = serde_json::from_str(&structured).map_err(|e| { + Status::aborted(format!("parse structured runtime data: {e}")) + })?; + Some(attestation_service::Data::Structured(structured)) + } + }, + None => None, + }; + + let init_data = match request.init_data { + Some(init_data) => match init_data { + crate::as_api::attestation_request::InitData::RawInitData(raw) => { + let raw_init = URL_SAFE_NO_PAD + .decode(raw) + .map_err(|e| Status::aborted(format!("base64 decode init data: {e}")))?; + Some(attestation_service::Data::Raw(raw_init)) + } + crate::as_api::attestation_request::InitData::StructuredInitData(structured) => { + let structured = serde_json::from_str(&structured) + .map_err(|e| Status::aborted(format!("parse structured init data: {e}")))?; + Some(attestation_service::Data::Structured(structured)) + } + }, + None => None, + }; + + let runtime_data_hash_algorithm = match request.runtime_data_hash_algorithm.is_empty() { + false => { + HashAlgorithm::try_from(&request.runtime_data_hash_algorithm[..]).map_err(|e| { + Status::aborted(format!("parse runtime data HashAlgorithm failed: {e}")) + })? + } + true => { + info!("No Runtime Data Hash Algorithm provided, use `sha384` by default."); + HashAlgorithm::Sha384 + } + }; + + let init_data_hash_algorithm = match request.init_data_hash_algorithm.is_empty() { + false => { + HashAlgorithm::try_from(&request.init_data_hash_algorithm[..]).map_err(|e| { + Status::aborted(format!("parse init data HashAlgorithm failed: {e}")) + })? + } + true => { + info!("No Init Data Hash Algorithm provided, use `sha384` by default."); + HashAlgorithm::Sha384 + } + }; + + let policy_ids = match request.policy_ids.is_empty() { + true => vec!["default".into()], + false => request.policy_ids, + }; + let attestation_token = self .read() .await .attestation_service .evaluate( - to_kbs_tee( - GrpcTee::from_i32(request.tee) - .ok_or_else(|| Status::aborted(format!("Invalid TEE {}", request.tee)))?, - ), - &request.nonce, - &request.evidence, + evidence, + tee, + runtime_data, + runtime_data_hash_algorithm, + init_data, + init_data_hash_algorithm, + policy_ids, ) .await - .map_err(|e| Status::aborted(format!("Attestation: {e}")))?; + .map_err(|e| Status::aborted(format!("Attestation: {e:?}")))?; debug!("Attestation Token: {}", &attestation_token); diff --git a/attestation-service/protos/attestation.proto b/attestation-service/protos/attestation.proto index dc38d56f6..499d80e8f 100644 --- a/attestation-service/protos/attestation.proto +++ b/attestation-service/protos/attestation.proto @@ -14,10 +14,70 @@ enum Tee { } message AttestationRequest { + // TEE enum. Specify the evidence type Tee tee = 1; - string nonce = 2; - string evidence = 3; + + // Base64 encoded evidence. The alphabet is URL_SAFE_NO_PAD. + // defined in https://datatracker.ietf.org/doc/html/rfc4648#section-5 + string evidence = 2; + + // Runtime Data used to check the binding relationship with report data in + // Evidence + oneof runtime_data { + // Base64 encoded runtime data slice. The whole string will be base64 + // decoded. The result one will then be accumulated into a digest which + // is used as the expected runtime data to check against the one inside + // evidence. + // + // The alphabet is URL_SAFE_NO_PAD. + // defined in https://datatracker.ietf.org/doc/html/rfc4648#section-5 + string raw_runtime_data = 3; + + // Runtime data in a JSON map. CoCoAS will rearrange each layer of the + // data JSON object in dictionary order by key, then serialize and output + // it into a compact string, and perform hash calculation on the whole + // to check against the one inside evidence. + // + // After the verification, the structured runtime data field will be included + // inside the token claims. + string structured_runtime_data = 4; + } + + // Init Data used to check the binding relationship with init data in + // Evidence + oneof init_data { + // Base64 encoded init data slice. The whole string will be base64 + // decoded. The result one will then be accumulated into a digest which + // is used as the expected init data to check against the one inside + // evidence. + // + // The alphabet is URL_SAFE_NO_PAD. + // defined in https://datatracker.ietf.org/doc/html/rfc4648#section-5 + string raw_init_data = 5; + + // Init data in a JSON map. CoCoAS will rearrange each layer of the + // data JSON object in dictionary order by key, then serialize and output + // it into a compact string, and perform hash calculation on the whole + // to check against the one inside evidence. + // + // After the verification, the structured init data field will be included + // inside the token claims. + string structured_init_data = 6; + } + + // Hash algorithm used to calculate runtime data. Currently can be "sha256", + // "sha384" or "sha512". If not specified, "sha384" will be selected. + string runtime_data_hash_algorithm = 7; + + // Hash algorithm used to calculate init data. Currently can be "sha256", + // "sha384" or "sha512". If not specified, "sha384" will be selected. + string init_data_hash_algorithm = 8; + + // List of IDs of the policy used to check evidence. If not provided, + // a "default" one will be used. + repeated string policy_ids = 9; } + message AttestationResponse { string attestation_token = 1; } From f141a7884d2c07143500b697f591b9e88a9e5b3a Mon Sep 17 00:00:00 2001 From: Xynnn007 Date: Fri, 24 Nov 2023 17:27:28 +0800 Subject: [PATCH 4/6] kbs: use new API of CoCo-AS New CoCo-AS API will decouple KBS protocol semantics. Also, this commit changes the hashed materials when connecting to CoCo-AS. Before this commit, only k_mod and k_exp of tee-pubkey field are protected integrity when doing remote attestation. The `kty` and `alg` field of the tee-pubkey are ignored. Signed-off-by: Xynnn007 --- kbs/proto/attestation.proto | 33 --------------------- kbs/src/api/build.rs | 3 +- kbs/src/api/src/attestation/coco/builtin.rs | 23 ++++++++++++-- kbs/src/api/src/attestation/coco/grpc.rs | 27 +++++++++++++---- kbs/src/api/src/http/resource.rs | 16 ++++++++++ 5 files changed, 59 insertions(+), 43 deletions(-) delete mode 100644 kbs/proto/attestation.proto diff --git a/kbs/proto/attestation.proto b/kbs/proto/attestation.proto deleted file mode 100644 index b83d2fe28..000000000 --- a/kbs/proto/attestation.proto +++ /dev/null @@ -1,33 +0,0 @@ -syntax = "proto3"; - -package attestation; - -enum Tee { - SEV = 0; - SGX = 1; - SNP = 2; - TDX = 3; - Sample = 4; - AzSnpVtpm = 5; - Csv = 6; - CCA = 7; -} - -message AttestationRequest { - Tee tee = 1; - string nonce = 2; - string evidence = 3; -} -message AttestationResponse { - string attestation_token = 1; -} - -message SetPolicyRequest { - string input = 1; -} -message SetPolicyResponse {} - -service AttestationService { - rpc AttestationEvaluate(AttestationRequest) returns (AttestationResponse) {}; - rpc SetAttestationPolicy(SetPolicyRequest) returns (SetPolicyResponse) {}; -} \ No newline at end of file diff --git a/kbs/src/api/build.rs b/kbs/src/api/build.rs index ede5cbc87..116cd9ae6 100644 --- a/kbs/src/api/build.rs +++ b/kbs/src/api/build.rs @@ -34,7 +34,8 @@ fn main() -> Result<(), String> { } #[cfg(feature = "tonic-build")] - tonic_build::compile_protos("../../proto/attestation.proto").map_err(|e| format!("{e}"))?; + tonic_build::compile_protos("../../../attestation-service/protos/attestation.proto") + .map_err(|e| format!("{e}"))?; Ok(()) } diff --git a/kbs/src/api/src/attestation/coco/builtin.rs b/kbs/src/api/src/attestation/coco/builtin.rs index 416e451fe..7dab65787 100644 --- a/kbs/src/api/src/attestation/coco/builtin.rs +++ b/kbs/src/api/src/attestation/coco/builtin.rs @@ -6,9 +6,11 @@ use crate::attestation::Attest; use anyhow::*; use async_trait::async_trait; use attestation_service::{ - config::Config as AsConfig, policy_engine::SetPolicyInput, AttestationService, + config::Config as AsConfig, policy_engine::SetPolicyInput, AttestationService, Data, + HashAlgorithm, }; -use kbs_types::Tee; +use kbs_types::{Attestation, Tee}; +use serde_json::json; pub struct Native { inner: AttestationService, @@ -23,7 +25,22 @@ impl Attest for Native { } async fn verify(&mut self, tee: Tee, nonce: &str, attestation: &str) -> Result { - self.inner.evaluate(tee, nonce, attestation).await + let attestation: Attestation = serde_json::from_str(attestation)?; + + // TODO: align with the guest-components/kbs-protocol side. + let runtime_data_plaintext = json!({"tee-pubkey": attestation.tee_pubkey, "nonce": nonce}); + + self.inner + .evaluate( + attestation.tee_evidence.into_bytes(), + tee, + Some(Data::Structured(runtime_data_plaintext)), + HashAlgorithm::Sha384, + None, + HashAlgorithm::Sha384, + vec!["default".into()], + ) + .await } } diff --git a/kbs/src/api/src/attestation/coco/grpc.rs b/kbs/src/api/src/attestation/coco/grpc.rs index ae73e588a..4c4c395dd 100644 --- a/kbs/src/api/src/attestation/coco/grpc.rs +++ b/kbs/src/api/src/attestation/coco/grpc.rs @@ -5,14 +5,16 @@ use crate::attestation::Attest; use anyhow::*; use async_trait::async_trait; -use kbs_types::Tee; +use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine}; +use kbs_types::{Attestation, Tee}; use log::info; use serde::Deserialize; +use serde_json::json; use tonic::transport::Channel; use self::attestation::{ - attestation_service_client::AttestationServiceClient, AttestationRequest, SetPolicyRequest, - Tee as GrpcTee, + attestation_request::RuntimeData, attestation_service_client::AttestationServiceClient, + AttestationRequest, SetPolicyRequest, Tee as GrpcTee, }; mod attestation { @@ -23,6 +25,8 @@ mod attestation { pub const DEFAULT_AS_ADDR: &str = "http://127.0.0.1:50004"; +pub const COCO_AS_HASH_ALGORITHM: &str = "sha384"; + fn to_grpc_tee(tee: Tee) -> GrpcTee { match tee { Tee::AzSnpVtpm => GrpcTee::AzSnpVtpm, @@ -85,10 +89,21 @@ impl Attest for Grpc { } async fn verify(&mut self, tee: Tee, nonce: &str, attestation: &str) -> Result { + let attestation: Attestation = serde_json::from_str(attestation)?; + + // TODO: align with the guest-components/kbs-protocol side. + let runtime_data_plaintext = json!({"tee-pubkey": attestation.tee_pubkey, "nonce": nonce}); + let runtime_data_plaintext = serde_json::to_string(&runtime_data_plaintext) + .context("CoCo AS client: serialize runtime data failed")?; + let req = tonic::Request::new(AttestationRequest { - tee: to_grpc_tee(tee) as i32, - nonce: String::from(nonce), - evidence: String::from(attestation), + tee: to_grpc_tee(tee).into(), + evidence: URL_SAFE_NO_PAD.encode(attestation.tee_evidence), + runtime_data_hash_algorithm: COCO_AS_HASH_ALGORITHM.into(), + init_data_hash_algorithm: COCO_AS_HASH_ALGORITHM.into(), + runtime_data: Some(RuntimeData::StructuredRuntimeData(runtime_data_plaintext)), + init_data: None, + policy_ids: vec!["default".to_string()], }); let token = self diff --git a/kbs/src/api/src/http/resource.rs b/kbs/src/api/src/http/resource.rs index 26ac8474c..dbb74b60a 100644 --- a/kbs/src/api/src/http/resource.rs +++ b/kbs/src/api/src/http/resource.rs @@ -47,6 +47,22 @@ pub(crate) async fn get_resource( })?; let pkey_value = claims + .get("customized_claims") + .ok_or(Error::AttestationClaimsParseFailed(String::from( + "No `customized_claims` in the attestation claims thus no `tee-pubkey`", + )))? + .as_object() + .ok_or(Error::AttestationClaimsParseFailed(String::from( + "`customized_claims` should be a JSON map", + )))? + .get("runtime_data") + .ok_or(Error::AttestationClaimsParseFailed(String::from( + "No `runtime_data` in the attestation claims thus no `tee-pubkey`", + )))? + .as_object() + .ok_or(Error::AttestationClaimsParseFailed(String::from( + "`runtime_data` should be a JSON map", + )))? .get("tee-pubkey") .ok_or(Error::AttestationClaimsParseFailed(String::from( "No `tee-pubkey` in the attestation claims", From 7f0c4e77bbfb0bc212d58976937998c18410e864 Mon Sep 17 00:00:00 2001 From: Xynnn007 Date: Fri, 24 Nov 2023 17:48:40 +0800 Subject: [PATCH 5/6] AS: fix documents Fixed the wrong guides to build a gRPC CoCo-AS. Also update the new format of attestation-token. Signed-off-by: Xynnn007 --- attestation-service/README.md | 60 +++++++++---------- attestation-service/bin/grpc-as/README.md | 39 ++++++++++-- .../bin/grpc-as/example.token.json | 51 ++++++++++++++++ .../tests/coco-as/grpc-request.json | 5 ++ 4 files changed, 118 insertions(+), 37 deletions(-) create mode 100644 attestation-service/bin/grpc-as/example.token.json create mode 100644 attestation-service/tests/coco-as/grpc-request.json diff --git a/attestation-service/README.md b/attestation-service/README.md index 13b9d2e7b..c1806a174 100644 --- a/attestation-service/README.md +++ b/attestation-service/README.md @@ -1,9 +1,9 @@ # Attestation Service -Attestation Service (AS for short) is a general function set that can verify TEE evidence. +Attestation Service (AS for short, also known as CoCo-AS) is a general function set that can verify TEE evidence. With Confidential Containers, the attestation service must run in an secure environment, outside of the guest node. -With remote attestation, Attesters (e.g. the [Attestation Agent](https://github.com/confidential-containers/attestation-agent)) running on the guest node will request a resource (e.g. a container image decryption key) from the [Key Broker Service (KBS)](https://github.com/confidential-containers/kbs)). +With remote attestation, Attesters (e.g. the [Attestation Agent](https://github.com/confidential-containers/guest-components/tree/main/attestation-agent)) running on the guest node will request a resource (e.g. a container image decryption key) from the [Key Broker Service (KBS)](https://github.com/confidential-containers/kbs). The KBS receives the attestation evidence from the client in TEE and forwards it to the Attestation Service (AS). The AS role is to verify the attestation evidence and provide Attestation Results token back to the KBS. Verifying the evidence is a two steps process: 1. Verify the evidence signature, and assess that it's been signed with a trusted key of TEE. @@ -11,8 +11,10 @@ The KBS receives the attestation evidence from the client in TEE and forwards it Those two steps are accomplished by respectively one of the [Verifier Drivers](#verifier-drivers) and the AS [Policy Engine](#policy-engine). The policy engine can be customized with different policy configurations. +In addition, CoCo-AS can also run independently as a remote attestation service which receives TEE evidence and returns the verification results as a token. + The AS can be built as a library (i.e. a Rust crate) by other confidential computing resources providers, like for example the KBS. -It can also run as a standalone binary, in which case its API is exposed through a set of [gRPC](https://grpc.io/) endpoints. +It can also run as a standalone binary, which exposes APIs in gRPC way. # Components @@ -23,7 +25,7 @@ The AS can be built and imported as a Rust crate into any project providing atte As the AS API is not yet fully stable, the AS crate needs to be imported from GitHub directly: ```toml -attestation-service = { git = "https://github.com/confidential-containers/attestation-service", branch = "main" } +attestation-service = { git = "https://github.com/confidential-containers/kbs", branch = "main" } ``` ## Server @@ -37,8 +39,8 @@ This project provides the Attestation Service binary program that can be run as Build and install AS components: ```shell -git clone https://github.com/confidential-containers/attestation-service -cd attestation-service +git clone https://github.com/confidential-containers/kbs +cd kbs/attestation-service make && make install ``` @@ -68,24 +70,13 @@ please refer to [the doc](./rvps/README.md#run-mode) for more details. ### Evidence format: -The attestation evidence is included in a [KBS defined Attestation payload](../kbs/docs/kbs_attestation_protocol.md#attestation): - -```json -{ - "tee-pubkey": $pubkey, - "tee-evidence": {} -} -``` - -- `tee-pubkey`: A JWK-formatted public key, generated by the client running in the HW-TEE. -For more details on the `tee-pubkey` format, see the [KBS protocol](../kbs/docs/kbs_attestation_protocol.md#key-format). - -- `tee-evidence`: The attestation evidence generated by the HW-TEE platform software and hardware in the AA's execution environment. -The tee-evidence formats depend on the TEE and are typically defined by the each TEE verifier driver of AS. - -**Note**: Verification Demander needs to specify the TEE type and pass `nonce` to Attestation-Service together with Evidence, -Hash of `nonce` and `tee-pubkey` should be embedded in report/quote in `tee-evidence`, so they can be signed by HW-TEE. -This mechanism ensures the freshness of Evidence and the authenticity of `tee-pubkey`. +The evidence format is different from TEE to TEE. Please refer to the concrete code definition for the specified format. +- Intel TDX: [TdxEvidence](./verifier/src/tdx/mod.rs) +- Intel SGX: [SgxEvidence](./verifier/src/sgx/mod.rs) +- AMD SNP: [SnpEvidence](./verifier/src/snp/mod.rs) +- Azure SNP vTPM: [Evidence](./verifier/src/az_snp_vtpm/mod.rs) +- Arm CCA: [CcaEvidence](./verifier/src/cca/mod.rs) +- Hygon CSV: [CsvEvidence](./verifier/src/csv/mod.rs) ### Attestation Results Token: @@ -99,12 +90,14 @@ Claims format of the attestation results token is: ```json { "iss": $issuer_name, - "jwk": $public_key, + "jwk": $public_key_used_to_sign_the_token, "exp": $expire_timestamp, "nbf": $notbefore_timestamp, - "tee-pubkey": $pubkey, + "policy-ids": $policy_ids_used_to_verify_the_evidence, "tcb-status": $parsed_evidence, - "evaluation-report": $report + "evaluation-reports": $reports_of_every_policy_specified, + "reference-data": $reference_data_used_to_check, + "customized_claims": $customized_claims, } ``` @@ -112,18 +105,19 @@ Claims format of the attestation results token is: * `jwk`: Public key to verify token signature. Must be in format of [JSON Web Key](https://datatracker.ietf.org/doc/html/rfc7517). * `exp`: Token expire time in Unix timestamp format. * `nbf`: Token effective time in Unix timestamp format. -* `tee-pubkey`: A JWK-formatted public key, generated by the client running in the HW-TEE. -For more details on the `tee-pubkey` format, see the [KBS protocol](../kbs/docs/kbs_attestation_protocol.md#key-format). * `tcb_status`: Contains HW-TEE informations and software measurements of AA's execution environment. The format is in the [doc](./docs/parsed_claims.md). -* `evaluation-report` : The output of the policy engine, it is AS policy's evaluation opinion on TEE evidence. +* `policy-ids`: The OPA policy ids used to verify the evidence. +* `evaluation-reports` : The outputs of the policy engine, they are AS policies' evaluation opinion on TEE evidence. +* `reference-data`: Reference values in a map used to enforce the OPA policy. +* `customized_claims`: Customized claims whose integrity is protected by binding its digest into the evidence. It will be a JSON map. ## Verifier Drivers -A verifier driver parse the HW-TEE specific `tee-evidence` data from the received attestation evidence, and performs the following tasks: +A verifier driver parse the HW-TEE specific attestation evidence, and performs the following tasks: -1. Verify HW-TEE hardware signature of the TEE quote/report in `tee-evidence`. +1. Verify HW-TEE hardware signature of the TEE quote/report in the evidence. -2. Resolve `tee-evidence`, and organize the TCB status into JSON claims to return. +2. Resolve the evidence, and organize the TCB status into JSON claims to return. Supported Verifier Drivers: diff --git a/attestation-service/bin/grpc-as/README.md b/attestation-service/bin/grpc-as/README.md index 0235115ec..98f30578b 100644 --- a/attestation-service/bin/grpc-as/README.md +++ b/attestation-service/bin/grpc-as/README.md @@ -14,8 +14,9 @@ Here are the steps of building and running gRPC Attestation Service: Build and install: ```shell -git clone https://github.com/confidential-containers/attestation-service -cd attestation-service +git clone https://github.com/confidential-containers/kbs +cd kbs/attestation-service +WORKDIR=$(pwd) make && make install ``` @@ -33,10 +34,40 @@ grpc-as --version Start Attestation Service and specify the listen port of its gRPC service: ```shell -grpc-as --socket 127.0.0.1:3000 +grpc-as --socket 127.0.0.1:50004 ``` If you want to see the runtime log, run: ```shell -RUST_LOG=debug grpc-as --socket 127.0.0.1:3000 +RUST_LOG=debug grpc-as --socket 127.0.0.1:50004 ``` + +### API + +The API of gRPC CoCo-AS is defined in the [proto](../../protos/attestation.proto). + +### Test + +For example, we can use an SGX evidence to test CoCo-AS (gRPC). + +```shell +# Use the following cmdline to install grpcurl +# go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest + +cd $WORKDIR + +REQ=$(cat tests/coco-as/grpc-request.json) + +grpcurl -plaintext -import-path protos -proto ./protos/attestation.proto -d @ 127.0.0.1:50004 attestation.AttestationService/AttestationEvaluate < Date: Tue, 12 Dec 2023 13:25:11 +0800 Subject: [PATCH 6/6] tools/client: use the fixed version of kbs-protocol Signed-off-by: Xynnn007 --- Cargo.lock | 41 ++++++------------------------------- kbs/tools/client/Cargo.toml | 3 ++- 2 files changed, 8 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a0adccb3..7bad2d485 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -560,11 +560,11 @@ dependencies = [ [[package]] name = "attester" version = "0.1.0" -source = "git+https://github.com/confidential-containers/guest-components?rev=8dd91e0#8dd91e0084805b916e5c27fb573a1de355de0be1" +source = "git+https://github.com/Xynnn007/guest-components.git?rev=b2f863f#b2f863ff86fb004a93bab1a0b7bd76c126652c2a" dependencies = [ "anyhow", "async-trait", - "az-snp-vtpm 0.2.3", + "az-snp-vtpm", "base64 0.21.5", "codicon", "csv-rs", @@ -665,26 +665,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "az-snp-vtpm" -version = "0.2.3" -source = "git+https://github.com/kinvolk/azure-cvm-tooling?rev=2c2e411#2c2e411dfb6f64fb4ffa4443213c5777de7af30d" -dependencies = [ - "bincode", - "clap 4.4.10", - "jsonwebkey", - "memoffset 0.8.0", - "rsa 0.8.2", - "serde", - "serde_json", - "sev", - "sha2", - "static_assertions", - "thiserror", - "tss-esapi", - "ureq", -] - [[package]] name = "az-snp-vtpm" version = "0.4.0" @@ -1256,7 +1236,7 @@ dependencies = [ [[package]] name = "crypto" version = "0.1.0" -source = "git+https://github.com/confidential-containers/guest-components?rev=8dd91e0#8dd91e0084805b916e5c27fb573a1de355de0be1" +source = "git+https://github.com/Xynnn007/guest-components.git?rev=b2f863f#b2f863ff86fb004a93bab1a0b7bd76c126652c2a" dependencies = [ "aes-gcm", "anyhow", @@ -2441,7 +2421,7 @@ dependencies = [ [[package]] name = "kbs_protocol" version = "0.1.0" -source = "git+https://github.com/confidential-containers/guest-components?rev=8dd91e0#8dd91e0084805b916e5c27fb573a1de355de0be1" +source = "git+https://github.com/Xynnn007/guest-components.git?rev=b2f863f#b2f863ff86fb004a93bab1a0b7bd76c126652c2a" dependencies = [ "anyhow", "async-trait", @@ -2643,15 +2623,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "memoffset" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" -dependencies = [ - "autocfg", -] - [[package]] name = "memoffset" version = "0.9.0" @@ -3652,7 +3623,7 @@ dependencies = [ [[package]] name = "resource_uri" version = "0.1.0" -source = "git+https://github.com/confidential-containers/guest-components?rev=8dd91e0#8dd91e0084805b916e5c27fb573a1de355de0be1" +source = "git+https://github.com/Xynnn007/guest-components.git?rev=b2f863f#b2f863ff86fb004a93bab1a0b7bd76c126652c2a" dependencies = [ "anyhow", "serde", @@ -5117,7 +5088,7 @@ dependencies = [ "asn1-rs", "assert-json-diff", "async-trait", - "az-snp-vtpm 0.4.0", + "az-snp-vtpm", "base64 0.21.5", "bincode", "byteorder", diff --git a/kbs/tools/client/Cargo.toml b/kbs/tools/client/Cargo.toml index 37aae0c2e..3fe268606 100644 --- a/kbs/tools/client/Cargo.toml +++ b/kbs/tools/client/Cargo.toml @@ -19,7 +19,8 @@ base64.workspace = true clap = { version = "4.0.29", features = ["derive"] } env_logger.workspace = true jwt-simple = "0.11.4" -kbs_protocol = { git = "https://github.com/confidential-containers/guest-components", rev = "8dd91e0" } +# TODO: change the git repo after https://github.com/confidential-containers/guest-components/pull/406 is merged. +kbs_protocol = { git = "https://github.com/Xynnn007/guest-components.git", rev = "b2f863f" } log.workspace = true reqwest = { version = "0.11.18", default-features = false, features = ["cookies", "json"] } serde = { version = "1.0", features = ["derive"] }