From 606e91387cbdbc958fa48de22337895fb40aac65 Mon Sep 17 00:00:00 2001 From: steven ygui Date: Fri, 8 Nov 2024 10:19:55 +0800 Subject: [PATCH] backport patches (cherry picked from commit 3beb59ff4b4e1ef8cd12e4afdee39d1a9081c37d) --- 0080-add-attestation-service.patch | 4626 ++++++++++++++++++++++++++++ secGear.spec | 7 +- 2 files changed, 4631 insertions(+), 2 deletions(-) create mode 100644 0080-add-attestation-service.patch diff --git a/0080-add-attestation-service.patch b/0080-add-attestation-service.patch new file mode 100644 index 0000000..93e64d0 --- /dev/null +++ b/0080-add-attestation-service.patch @@ -0,0 +1,4626 @@ +From 85f0bca3d385699ffca8d15c70ff9ac563d34512 Mon Sep 17 00:00:00 2001 +From: xuraoqing +Date: Thu, 22 Aug 2024 22:46:30 +0800 +Subject: [PATCH] add attestation service + +--- + service/attestation/.gitignore | 3 + + .../attestation/attestation-agent/Cargo.toml | 29 ++ + .../attestation/attestation-agent/README.md | 5 + + .../attestation-agent/agent/Cargo.toml | 47 ++ + .../agent/attestation-agent.conf | 7 + + .../agent/src/bin/aa-test/main.rs | 195 ++++++++ + .../agent/src/bin/generate-headers/main.rs | 14 + + .../attestation-agent/agent/src/lib.rs | 387 ++++++++++++++++ + .../attestation-agent/agent/src/main.rs | 67 +++ + .../agent/src/restapi/mod.rs | 140 ++++++ + .../attestation-agent/agent/src/result/mod.rs | 50 ++ + .../attestation-agent/attester/Cargo.toml | 18 + + .../attester/src/itrustee/itrustee.rs | 51 +++ + .../attester/src/itrustee/mod.rs | 130 ++++++ + .../attestation-agent/attester/src/lib.rs | 79 ++++ + .../attester/src/virtcca/mod.rs | 93 ++++ + .../attester/src/virtcca/virtcca.rs | 109 +++++ + .../attestation-agent/token/Cargo.toml | 13 + + .../attestation-agent/token/src/lib.rs | 114 +++++ + .../attestation-service/Cargo.toml | 42 ++ + .../attestation/attestation-service/README.md | 6 + + .../attestation-service/policy/Cargo.toml | 12 + + .../attestation-service/policy/src/lib.rs | 181 ++++++++ + .../policy/src/opa/default_itrustee.rego | 10 + + .../policy/src/opa/default_vcca.rego | 10 + + .../attestation-service/policy/src/opa/mod.rs | 167 +++++++ + .../policy/src/policy_engine.rs | 73 +++ + .../attestation-service/reference/Cargo.toml | 16 + + .../reference/src/extractor/mod.rs | 30 ++ + .../attestation-service/reference/src/lib.rs | 141 ++++++ + .../reference/src/local_fs/mod.rs | 87 ++++ + .../reference/src/reference/mod.rs | 147 ++++++ + .../reference/src/store/mod.rs | 19 + + .../attestation-service/service/Cargo.toml | 35 ++ + .../service/attestation-service.conf | 9 + + .../attestation-service/service/src/lib.rs | 204 +++++++++ + .../attestation-service/service/src/main.rs | 76 ++++ + .../service/src/restapi/mod.rs | 139 ++++++ + .../service/src/result/mod.rs | 55 +++ + .../service/src/session.rs | 58 +++ + .../attestation-service/tests/Cargo.toml | 9 + + .../attestation-service/tests/src/lib.rs | 166 +++++++ + .../attestation-service/token/Cargo.toml | 13 + + .../attestation-service/token/src/lib.rs | 115 +++++ + .../attestation-service/verifier/Cargo.toml | 27 ++ + .../verifier/src/itrustee/itrustee.rs | 53 +++ + .../verifier/src/itrustee/mod.rs | 76 ++++ + .../attestation-service/verifier/src/lib.rs | 80 ++++ + .../verifier/src/virtcca/ima.rs | 91 ++++ + .../verifier/src/virtcca/mod.rs | 427 ++++++++++++++++++ + .../attestation/attestation-types/Cargo.toml | 8 + + .../attestation/attestation-types/src/lib.rs | 52 +++ + 52 files changed, 4185 insertions(+) + create mode 100644 service/attestation/.gitignore + create mode 100644 service/attestation/attestation-agent/Cargo.toml + create mode 100644 service/attestation/attestation-agent/README.md + create mode 100644 service/attestation/attestation-agent/agent/Cargo.toml + create mode 100644 service/attestation/attestation-agent/agent/attestation-agent.conf + create mode 100644 service/attestation/attestation-agent/agent/src/bin/aa-test/main.rs + create mode 100644 service/attestation/attestation-agent/agent/src/bin/generate-headers/main.rs + create mode 100644 service/attestation/attestation-agent/agent/src/lib.rs + create mode 100644 service/attestation/attestation-agent/agent/src/main.rs + create mode 100644 service/attestation/attestation-agent/agent/src/restapi/mod.rs + create mode 100644 service/attestation/attestation-agent/agent/src/result/mod.rs + create mode 100644 service/attestation/attestation-agent/attester/Cargo.toml + create mode 100644 service/attestation/attestation-agent/attester/src/itrustee/itrustee.rs + create mode 100644 service/attestation/attestation-agent/attester/src/itrustee/mod.rs + create mode 100644 service/attestation/attestation-agent/attester/src/lib.rs + create mode 100644 service/attestation/attestation-agent/attester/src/virtcca/mod.rs + create mode 100644 service/attestation/attestation-agent/attester/src/virtcca/virtcca.rs + create mode 100644 service/attestation/attestation-agent/token/Cargo.toml + create mode 100644 service/attestation/attestation-agent/token/src/lib.rs + create mode 100644 service/attestation/attestation-service/Cargo.toml + create mode 100644 service/attestation/attestation-service/README.md + create mode 100644 service/attestation/attestation-service/policy/Cargo.toml + create mode 100644 service/attestation/attestation-service/policy/src/lib.rs + create mode 100644 service/attestation/attestation-service/policy/src/opa/default_itrustee.rego + create mode 100644 service/attestation/attestation-service/policy/src/opa/default_vcca.rego + create mode 100644 service/attestation/attestation-service/policy/src/opa/mod.rs + create mode 100644 service/attestation/attestation-service/policy/src/policy_engine.rs + create mode 100644 service/attestation/attestation-service/reference/Cargo.toml + create mode 100644 service/attestation/attestation-service/reference/src/extractor/mod.rs + create mode 100644 service/attestation/attestation-service/reference/src/lib.rs + create mode 100644 service/attestation/attestation-service/reference/src/local_fs/mod.rs + create mode 100644 service/attestation/attestation-service/reference/src/reference/mod.rs + create mode 100644 service/attestation/attestation-service/reference/src/store/mod.rs + create mode 100644 service/attestation/attestation-service/service/Cargo.toml + create mode 100644 service/attestation/attestation-service/service/attestation-service.conf + create mode 100644 service/attestation/attestation-service/service/src/lib.rs + create mode 100644 service/attestation/attestation-service/service/src/main.rs + create mode 100644 service/attestation/attestation-service/service/src/restapi/mod.rs + create mode 100644 service/attestation/attestation-service/service/src/result/mod.rs + create mode 100644 service/attestation/attestation-service/service/src/session.rs + create mode 100644 service/attestation/attestation-service/tests/Cargo.toml + create mode 100644 service/attestation/attestation-service/tests/src/lib.rs + create mode 100644 service/attestation/attestation-service/token/Cargo.toml + create mode 100644 service/attestation/attestation-service/token/src/lib.rs + create mode 100644 service/attestation/attestation-service/verifier/Cargo.toml + create mode 100644 service/attestation/attestation-service/verifier/src/itrustee/itrustee.rs + create mode 100644 service/attestation/attestation-service/verifier/src/itrustee/mod.rs + create mode 100644 service/attestation/attestation-service/verifier/src/lib.rs + create mode 100644 service/attestation/attestation-service/verifier/src/virtcca/ima.rs + create mode 100644 service/attestation/attestation-service/verifier/src/virtcca/mod.rs + create mode 100644 service/attestation/attestation-types/Cargo.toml + create mode 100644 service/attestation/attestation-types/src/lib.rs + +diff --git a/service/attestation/.gitignore b/service/attestation/.gitignore +new file mode 100644 +index 0000000..8094f6e +--- /dev/null ++++ b/service/attestation/.gitignore +@@ -0,0 +1,3 @@ ++.vscode ++target ++Cargo.lock +diff --git a/service/attestation/attestation-agent/Cargo.toml b/service/attestation/attestation-agent/Cargo.toml +new file mode 100644 +index 0000000..bdc7b12 +--- /dev/null ++++ b/service/attestation/attestation-agent/Cargo.toml +@@ -0,0 +1,29 @@ ++[workspace] ++resolver = "2" ++members = [ ++ "agent", ++ "attester", ++ "token" ++] ++ ++[workspace.dependencies] ++anyhow = "1.0" ++config = "0.14.0" ++serde = { version = "1.0", features = ["derive"] } ++serde_json = "1.0" ++rand = "0.8.5" ++base64-url = "3.0.0" ++async-trait = "0.1.78" ++tokio = {version = "1.0", features = ["rt"]} ++log = "0.4.14" ++env_logger = "0.9" ++safer-ffi = {version = "0.1.8", features = ["alloc"]} ++futures = "0.3.30" ++reqwest = { version = "0.12", features = ["cookies", "json"] } ++jsonwebtoken = "9.3.0" ++thiserror = "1.0" ++actix-web = "4.5" ++clap = { version = "4.5.7", features = ["derive"] } ++ ++verifier = {path = "../attestation-service/verifier", default-features = false} ++attestation-types = {path = "../attestation-types"} +diff --git a/service/attestation/attestation-agent/README.md b/service/attestation/attestation-agent/README.md +new file mode 100644 +index 0000000..0157e59 +--- /dev/null ++++ b/service/attestation/attestation-agent/README.md +@@ -0,0 +1,5 @@ ++# Attestation Agent ++The Attestation Agent is deployed on the TEE node, provide get_evidence, get_token, verify_evidece interface, etc. ++ ++# Overview ++TODO +diff --git a/service/attestation/attestation-agent/agent/Cargo.toml b/service/attestation/attestation-agent/agent/Cargo.toml +new file mode 100644 +index 0000000..e29f89b +--- /dev/null ++++ b/service/attestation/attestation-agent/agent/Cargo.toml +@@ -0,0 +1,47 @@ ++[package] ++name = "attestation-agent" ++version = "0.1.0" ++edition = "2021" ++ ++[[bin]] ++name = "aa-test" ++ ++[[bin]] ++name = "generate-headers" ++required-features = ["headers"] ++ ++[lib] ++name = "attestation_agent" ++crate-type = ["lib", "cdylib"] ++ ++[features] ++no_as = [] ++itrustee-attester = ["attester/itrustee-attester"] ++virtcca-attester = ["attester/virtcca-attester"] ++all-attester = ["attester/itrustee-attester", "attester/virtcca-attester"] ++itrustee-verifier = ["verifier/itrustee-verifier"] ++virtcca-verifier = ["verifier/virtcca-verifier"] ++all-verifier = ["verifier/itrustee-verifier", "verifier/virtcca-verifier"] ++headers = ["safer-ffi/headers"] ++ ++[dependencies] ++anyhow.workspace = true ++config.workspace = true ++serde.workspace = true ++serde_json.workspace = true ++rand.workspace = true ++async-trait.workspace = true ++tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } ++log.workspace = true ++env_logger.workspace = true ++safer-ffi.workspace = true ++futures.workspace = true ++reqwest = { workspace = true, features = ["json"] } ++base64-url.workspace = true ++thiserror.workspace = true ++actix-web.workspace = true ++clap.workspace = true ++ ++attester = { path = "../attester" } ++token_verifier = { path = "../token" } ++verifier = { workspace = true, features = ["no_as"], optional = true } +diff --git a/service/attestation/attestation-agent/agent/attestation-agent.conf b/service/attestation/attestation-agent/agent/attestation-agent.conf +new file mode 100644 +index 0000000..0d68972 +--- /dev/null ++++ b/service/attestation/attestation-agent/agent/attestation-agent.conf +@@ -0,0 +1,7 @@ ++{ ++ "svr_url": "http://192.168.66.88:8888", ++ "token_cfg": { ++ "cert": "/home/cert/as_cert.pem", ++ "iss": "oeas" ++ } ++} +\ No newline at end of file +diff --git a/service/attestation/attestation-agent/agent/src/bin/aa-test/main.rs b/service/attestation/attestation-agent/agent/src/bin/aa-test/main.rs +new file mode 100644 +index 0000000..58fc389 +--- /dev/null ++++ b/service/attestation/attestation-agent/agent/src/bin/aa-test/main.rs +@@ -0,0 +1,195 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++//! This is a test bin, test get evidence and verify ++//! on kunpeng platform, libqca has white ta lists, need copy target/debug/attestation-agent to /vendor/bin/ ++use tokio; ++use env_logger; ++use serde_json::json; ++use reqwest; ++ ++const TEST_THREAD_NUM: i64 = 1; // multi thread num ++ ++#[tokio::main] ++async fn main() { ++ env_logger::init(); ++ let mut handles = Vec::with_capacity(TEST_THREAD_NUM as usize); ++ for i in 0..TEST_THREAD_NUM { ++ let t = tokio::spawn(async move {aa_proc(i).await;}); ++ handles.push(t); ++ } ++ ++ for handle in handles { ++ let _ = tokio::join!(handle); ++ } ++ println!("main stop"); ++} ++ ++async fn aa_proc(i: i64) { ++ println!("attestation_proc {} start", i); ++ ++ // get challenge ++ let client = reqwest::Client::new(); ++ let challenge_endpoint = "http://127.0.0.1:8081/challenge"; ++ let res = client ++ .get(challenge_endpoint) ++ .header("Content-Type", "application/json") ++ .header("content-length", 0) ++ //.json(&request_body) ++ .send() ++ .await ++ .unwrap(); ++ ++ let challenge = match res.status() { ++ reqwest::StatusCode::OK => { ++ let respone = res.text().await.unwrap(); ++ println!("get challenge success, AA Response: {:?}", respone); ++ respone ++ } ++ status => { ++ println!("get challenge Failed, AA Response: {:?}", status); ++ return; ++ } ++ }; ++ ++ // get evidence ++ let request_body = json!({ ++ "challenge": challenge, ++ "uuid": String::from("f68fd704-6eb1-4d14-b218-722850eb3ef0"), ++ }); ++ ++ let attest_endpoint = "http://127.0.0.1:8081/evidence"; ++ let res = client ++ .get(attest_endpoint) ++ .header("Content-Type", "application/json") ++ .json(&request_body) ++ .send() ++ .await ++ .unwrap(); ++ ++ let evidence = match res.status() { ++ reqwest::StatusCode::OK => { ++ let respone = res.text().await.unwrap(); ++ println!("get evidence success, AA Response: {:?}", respone); ++ respone ++ } ++ status => { ++ println!("get evidence Failed, AA Response: {:?}", status); ++ return; ++ } ++ }; ++ // verify evidence with no challenge ++ #[cfg(not(feature = "no_as"))] ++ { ++ let request_body = json!({ ++ "challenge": "", ++ "evidence": evidence, ++ }); ++ ++ let res = client ++ .post(attest_endpoint) ++ .header("Content-Type", "application/json") ++ .json(&request_body) ++ .send() ++ .await ++ .unwrap(); ++ ++ match res.status() { ++ reqwest::StatusCode::OK => { ++ let respone = res.text().await.unwrap(); ++ println!("verify evidence with no challenge success, AA Response: {:?}", respone); ++ } ++ status => { ++ println!("verify evidence with no challenge Failed, AA Response: {:?}", status); ++ } ++ } ++ } ++ // verify evidence with challenge ++ let request_body = json!({ ++ "challenge": challenge, ++ "evidence": evidence, ++ }); ++ ++ let res = client ++ .post(attest_endpoint) ++ .header("Content-Type", "application/json") ++ .json(&request_body) ++ .send() ++ .await ++ .unwrap(); ++ ++ match res.status() { ++ reqwest::StatusCode::OK => { ++ let respone = res.text().await.unwrap(); ++ println!("verify evidence success, AA Response: {:?}", respone); ++ } ++ status => { ++ println!("verify evidence Failed, AA Response: {:?}", status); ++ } ++ } ++ ++ #[cfg(not(feature = "no_as"))] ++ { ++ // get token ++ let token_endpoint = "http://127.0.0.1:8081/token"; ++ let request_body = json!({ ++ "challenge": challenge, ++ "uuid": String::from("f68fd704-6eb1-4d14-b218-722850eb3ef0"), ++ }); ++ ++ let res = client ++ .get(token_endpoint) ++ .header("Content-Type", "application/json") ++ .json(&request_body) ++ .send() ++ .await ++ .unwrap(); ++ ++ let token = match res.status() { ++ reqwest::StatusCode::OK => { ++ let respone = res.text().await.unwrap(); ++ println!("get token success, AA Response: {:?}", respone); ++ respone ++ } ++ status => { ++ println!("get token Failed, AA Response: {:?}", status); ++ return; ++ } ++ }; ++ ++ // verify token ++ let request_body = json!({ ++ "token": token, ++ }); ++ ++ let res = client ++ .post(token_endpoint) ++ .header("Content-Type", "application/json") ++ .json(&request_body) ++ .send() ++ .await ++ .unwrap(); ++ ++ match res.status() { ++ reqwest::StatusCode::OK => { ++ let respone = res.text().await.unwrap(); ++ println!("verify token success, AA Response: {:?}", respone); ++ } ++ status => { ++ println!("verify token Failed, AA Response: {:?}", status); ++ } ++ } ++ } ++ ++ ++ println!("attestation_proc {} end", i); ++} +\ No newline at end of file +diff --git a/service/attestation/attestation-agent/agent/src/bin/generate-headers/main.rs b/service/attestation/attestation-agent/agent/src/bin/generate-headers/main.rs +new file mode 100644 +index 0000000..f3f62c9 +--- /dev/null ++++ b/service/attestation/attestation-agent/agent/src/bin/generate-headers/main.rs +@@ -0,0 +1,14 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++fn main() -> ::std::io::Result<()> { ++ attestation_agent::generate_headers() ++} +diff --git a/service/attestation/attestation-agent/agent/src/lib.rs b/service/attestation/attestation-agent/agent/src/lib.rs +new file mode 100644 +index 0000000..4ff9b58 +--- /dev/null ++++ b/service/attestation/attestation-agent/agent/src/lib.rs +@@ -0,0 +1,387 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++//! Attestation Agent ++//! ++//! This crate provides some APIs to get and verify the TEE evidence. ++//! Current supports kunpeng itrustee and virtcca TEE types. ++ ++use anyhow::{Result, bail, anyhow}; ++use log; ++use serde::{Serialize, Deserialize}; ++use async_trait::async_trait; ++use std::fs::File; ++use std::path::Path; ++use rand::RngCore; ++ ++use attester::{Attester, AttesterAPIs}; ++use token_verifier::{TokenVerifyConfig, TokenVerifier, TokenRawData}; ++ ++pub mod result; ++use result::Error; ++pub type TeeClaim = serde_json::Value; ++ ++#[cfg(feature = "no_as")] ++use verifier::{Verifier, VerifierAPIs}; ++ ++#[cfg(not(feature = "no_as"))] ++use {serde_json::json, reqwest, base64_url}; ++ ++pub use attester::EvidenceRequest; ++ ++pub type AsTokenClaim = TokenRawData; ++ ++pub const DEFAULT_AACONFIG_FILE: &str = "/etc/attestation/attestation-agent/attestation-agent.conf"; ++pub struct TokenRequest { ++ pub ev_req: EvidenceRequest, ++ pub policy_id: Option>, ++} ++ ++#[async_trait] ++pub trait AttestationAgentAPIs { ++ async fn get_challenge(&self) -> Result; ++ ++ /// `get_evidence`: get hardware TEE signed evidence due to given user_data, ++ /// such as input random challenge to prevent replay attacks ++ async fn get_evidence(&self, user_data: EvidenceRequest) -> Result>; ++ ++ /// `verify_evidence`: verify the integrity of TEE evidence and evaluate the ++ /// claims against the supplied reference values ++ async fn verify_evidence(&self, ++ challenge: &[u8], ++ evidence: &[u8], ++ policy_id: Option> ++ ) -> Result; ++ ++ //#[cfg(not(feature = "no_as"))] ++ async fn get_token(&self, user_data: TokenRequest) -> Result; ++ ++ async fn verify_token(&self, token: String) -> Result; ++} ++ ++#[async_trait] ++impl AttestationAgentAPIs for AttestationAgent { ++ // no_as generate by agent; has as generate by as ++ async fn get_challenge(&self) -> Result { ++ #[cfg(feature = "no_as")] ++ return self.generate_challenge_local().await; ++ ++ #[cfg(not(feature = "no_as"))] ++ return self.get_challenge_from_as().await; ++ } ++ async fn get_evidence(&self, user_data: EvidenceRequest) -> Result> { ++ Attester::default().tee_get_evidence(user_data).await ++ } ++ async fn verify_evidence(&self, ++ challenge: &[u8], ++ evidence: &[u8], ++ _policy_id: Option> ++ ) -> Result { ++ #[cfg(feature = "no_as")] ++ { ++ let ret = Verifier::default().verify_evidence(challenge, evidence).await; ++ match ret { ++ Ok(tee_claim) => Ok(tee_claim), ++ Err(e) => { ++ log::error!("attestation agent verify evidence with no as failed:{:?}", e); ++ Err(e) ++ }, ++ } ++ } ++ ++ #[cfg(not(feature = "no_as"))] ++ { ++ let ret = self.verify_evidence_by_as(challenge, evidence, _policy_id).await; ++ match ret { ++ Ok(token) => { self.token_to_teeclaim(token).await }, ++ Err(e) => { ++ log::error!("verify evidence with as failed:{:?}", e); ++ Err(e) ++ }, ++ } ++ } ++ } ++ ++ async fn get_token(&self, user_data: TokenRequest) -> Result { ++ #[cfg(feature = "no_as")] ++ { ++ return Ok("no as in not supprot get token".to_string()); ++ } ++ // todo token 有效期内,不再重新获取报告 ++ #[cfg(not(feature = "no_as"))] ++ { ++ let evidence = self.get_evidence(user_data.ev_req.clone()).await?; ++ let challenge = &user_data.ev_req.challenge; ++ let policy_id = user_data.policy_id; ++ // request as ++ return self.verify_evidence_by_as(challenge, &evidence, policy_id).await; ++ } ++ } ++ ++ async fn verify_token(&self, token: String) -> Result { ++ let verifier = TokenVerifier::new(self.config.token_cfg.clone())?; ++ let result = verifier.verify(&token); ++ match result { ++ Ok(raw_token) => Ok(raw_token as AsTokenClaim), ++ Err(e) => bail!("verify token failed {:?}", e), ++ } ++ } ++} ++ ++#[derive(Clone, Debug, Serialize, Deserialize)] ++struct AAConfig { ++ svr_url: String, // Attestation Service url ++ token_cfg: TokenVerifyConfig, ++} ++ ++impl Default for AAConfig { ++ fn default() -> Self { ++ Self { ++ svr_url: String::from("http://127.0.0.1:8080"), ++ token_cfg: TokenVerifyConfig::default(), ++ } ++ } ++} ++ ++impl TryFrom<&Path> for AAConfig { ++ /// Load `AAConfig` from a configuration file like: ++ /// { ++ /// "svr_url": "http://127.0.0.1:8080", ++ /// "token_cfg": { ++ /// "cert": "/etc/attestation/attestation-agent/as_cert.pem", ++ /// "iss": "oeas" ++ /// } ++ /// } ++ type Error = anyhow::Error; ++ fn try_from(config_path: &Path) -> Result { ++ let file = File::open(config_path).unwrap(); ++ serde_json::from_reader::(file).map_err(|e| anyhow!("invalid aaconfig {e}")) ++ } ++} ++ ++#[derive(Debug)] ++pub struct AttestationAgent { ++ config: AAConfig, ++ client: reqwest::Client, ++} ++ ++#[allow(dead_code)] ++impl AttestationAgent { ++ pub fn new(conf_path: Option) -> Result { ++ let config = match conf_path { ++ Some(conf_path) => { ++ log::info!("Attestation Agent config file:{conf_path}"); ++ AAConfig::try_from(Path::new(&conf_path))? ++ } ++ None => { ++ log::warn!("No Attestation Agent config file specified. Using a default config"); ++ AAConfig::default() ++ } ++ }; ++ let client = reqwest::ClientBuilder::new() ++ .cookie_store(true) ++ .user_agent("attestation-agent-client") ++ .build() ++ .map_err(|e| result::Error::AttestationAgentError(format!("build http client {e}")))?; ++ Ok(AttestationAgent { ++ config, ++ client, ++ }) ++ } ++ ++ #[cfg(not(feature = "no_as"))] ++ async fn verify_evidence_by_as(&self, ++ challenge: &[u8], ++ evidence: &[u8], ++ policy_id: Option> ++ ) -> Result { ++ let request_body = json!({ ++ "challenge": base64_url::encode(challenge), ++ "evidence": base64_url::encode(evidence), ++ "policy_id": policy_id, ++ }); ++ ++ let attest_endpoint = format!("{}/attestation", self.config.svr_url); ++ let res = self.client ++ .post(attest_endpoint) ++ .header("Content-Type", "application/json") ++ .json(&request_body) ++ .send() ++ .await?; ++ ++ match res.status() { ++ reqwest::StatusCode::OK => { ++ let token = res.text().await?; ++ log::debug!("Remote Attestation success, AS Response: {:?}", token); ++ Ok(token) ++ } ++ _ => { ++ bail!("Remote Attestation Failed, AS Response: {:?}", res.text().await?); ++ } ++ } ++ } ++ ++ #[cfg(not(feature = "no_as"))] ++ async fn token_to_teeclaim(&self, token: String) -> Result { ++ let ret = self.verify_token(token).await; ++ match ret { ++ Ok(token) => { ++ let token_claim: serde_json::Value = serde_json::from_slice(token.claim.as_bytes())?; ++ let tee_claim = json!({ ++ "tee": token_claim["tee"].clone(), ++ "payload" : token_claim["tcb_status"].clone(), ++ }); ++ Ok(tee_claim as TeeClaim) ++ }, ++ Err(e) => { ++ log::error!("token to teeclaim failed:{:?}", e); ++ Err(e) ++ }, ++ } ++ } ++ ++ async fn generate_challenge_local(&self) -> Result { ++ let mut nonce: [u8; 32] = [0; 32]; ++ rand::thread_rng().fill_bytes(&mut nonce); ++ Ok(base64_url::encode(&nonce)) ++ } ++ async fn get_challenge_from_as(&self) -> Result { ++ let challenge_endpoint = format!("{}/challenge", self.config.svr_url); ++ let res = self.client ++ .get(challenge_endpoint) ++ .header("Content-Type", "application/json") ++ .header("content-length", 0) ++ //.json(&request_body) ++ .send() ++ .await?; ++ let challenge = match res.status() { ++ reqwest::StatusCode::OK => { ++ let respone = res.json().await.unwrap(); ++ log::info!("get challenge success, AS Response: {:?}", respone); ++ respone ++ } ++ status => { ++ log::info!("get challenge Failed, AS Response: {:?}", status); ++ bail!("get challenge Failed") ++ } ++ }; ++ Ok(challenge) ++ } ++} ++ ++ ++// attestation agent c interface ++use safer_ffi::prelude::*; ++use futures::executor::block_on; ++use tokio::runtime::Runtime; ++ ++#[ffi_export] ++pub fn get_report(c_challenge: Option<&repr_c::Vec>, c_ima: &repr_c::TaggedOption) -> repr_c::Vec { ++ env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); ++ log::debug!("input challenge: {:?}, ima: {:?}", c_challenge, c_ima); ++ let ima = match c_ima { ++ repr_c::TaggedOption::None => false, ++ repr_c::TaggedOption::Some(ima) => *ima, ++ }; ++ let challenge = match c_challenge { ++ None => {log::error!("challenge is null"); return Vec::new().into();}, ++ Some(cha) => cha.clone().to_vec(), ++ }; ++ ++ let input: EvidenceRequest = EvidenceRequest { ++ uuid: "f68fd704-6eb1-4d14-b218-722850eb3ef0".to_string(), ++ challenge: challenge, ++ ima: Some(ima), ++ }; ++ ++ let fut = async { ++ AttestationAgent::new(Some(DEFAULT_AACONFIG_FILE.to_string())).unwrap().get_evidence(input).await ++ }; ++ let report: Vec = match block_on(fut) { ++ Ok(report) => report, ++ Err(e) => { ++ log::error!("get report failed {:?}", e); ++ Vec::new() ++ }, ++ }; ++ ++ report.into() ++} ++ ++#[ffi_export] ++pub fn verify_report(c_challenge: Option<&repr_c::Vec>, report: Option<&repr_c::Vec>) -> repr_c::String { ++ let challenge = match c_challenge { ++ None => { ++ log::error!("challenge is null"); ++ return "".to_string().into(); ++ }, ++ Some(cha) => cha.clone().to_vec(), ++ }; ++ let report = match report { ++ None => { ++ log::error!("report is null"); ++ return "".to_string().into(); ++ }, ++ Some(report) => report.clone().to_vec(), ++ }; ++ let rt = Runtime::new().unwrap(); ++ let fut = async {AttestationAgent::new(Some(DEFAULT_AACONFIG_FILE.to_string())).unwrap().verify_evidence( ++ &challenge, &report, None).await}; ++ let ret = rt.block_on(fut); ++ ++ let ret = match ret { ++ Ok(claim) => { ++ log::debug!("claim: {:?}", claim); ++ claim.to_string() ++ }, ++ Err(e) =>{ ++ log::error!("{e}"); ++ "".to_string() ++ }, ++ }; ++ ++ return ret.into(); ++} ++ ++#[ffi_export] ++pub fn free_rust_vec(vec: repr_c::Vec) { ++ drop(vec); ++} ++ ++// The following function is only necessary for the header generation. ++#[cfg(feature = "headers")] ++pub fn generate_headers() -> ::std::io::Result<()> { ++ ::safer_ffi::headers::builder() ++ .to_file("./c_header/rust_attestation_agent.h")? ++ .generate() ++} ++ ++ ++#[cfg(test)] ++mod tests { ++ use crate::*; ++ ++ #[test] ++ fn aa_new_no_conf_path() { ++ let aa = AttestationAgent::new(None).unwrap(); ++ assert_eq!(aa.config.svr_url, "http://127.0.0.1:8080"); ++ assert_eq!(aa.config.token_cfg.cert, "/etc/attestation/attestation-agent/as_cert.pem"); ++ assert_eq!(aa.config.token_cfg.iss, "openEulerAS"); ++ } ++ ++ #[test] ++ fn aa_new_with_example_conf() { ++ let aa = AttestationAgent::new(Some("attestation-agent.conf".to_string())).unwrap(); ++ assert_eq!(aa.config.token_cfg.cert, "/home/cert/as_cert.pem"); ++ assert_eq!(aa.config.token_cfg.iss, "oeas"); ++ } ++} +diff --git a/service/attestation/attestation-agent/agent/src/main.rs b/service/attestation/attestation-agent/agent/src/main.rs +new file mode 100644 +index 0000000..76e63dc +--- /dev/null ++++ b/service/attestation/attestation-agent/agent/src/main.rs +@@ -0,0 +1,67 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use attestation_agent::{AttestationAgent, DEFAULT_AACONFIG_FILE}; ++mod restapi; ++use restapi::{get_challenge, get_evidence, verify_evidence, get_token, verify_token}; ++ ++use anyhow::Result; ++use env_logger; ++use actix_web::{web, App, HttpServer, HttpResponse}; ++use std::{net::{SocketAddr, IpAddr, Ipv4Addr}, sync::Arc}; ++use tokio::sync::RwLock; ++use clap::{Parser, command, arg}; ++ ++const DEFAULT_SOCKETADDR: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081); ++ ++#[derive(Parser, Debug)] ++#[command(version, about, long_about = None)] ++struct Cli { ++ /// Socket address to listen on ++ #[arg(short, long, default_value_t = DEFAULT_SOCKETADDR)] ++ socketaddr: SocketAddr, ++ ++ /// Load `AAConfig` from a configuration file like: ++ /// { ++ /// "svr_url": "http://127.0.0.1:8080", ++ /// "token_cfg": { ++ /// "cert": "/etc/attestation/attestation-agent/as_cert.pem", ++ /// "iss": "oeas" ++ /// } ++ /// } ++ #[arg(short, long, default_value_t = DEFAULT_AACONFIG_FILE.to_string())] ++ config: String, ++} ++ ++#[actix_web::main] ++async fn main() -> Result<()> { ++ env_logger::init_from_env(env_logger::Env::new().default_filter_or("debug")); ++ ++ let cli = Cli::parse(); ++ let server = AttestationAgent::new(Some(cli.config)).unwrap(); ++ ++ let service = web::Data::new(Arc::new(RwLock::new(server))); ++ HttpServer::new(move || { ++ App::new() ++ .app_data(web::Data::clone(&service)) ++ .service(get_challenge) ++ .service(get_evidence) ++ .service(verify_evidence) ++ .service(get_token) ++ .service(verify_token) ++ .default_service(web::to(|| HttpResponse::NotFound())) ++ }) ++ .bind((cli.socketaddr.ip().to_string(), cli.socketaddr.port()))? ++ .run() ++ .await?; ++ ++ Ok(()) ++} +\ No newline at end of file +diff --git a/service/attestation/attestation-agent/agent/src/restapi/mod.rs b/service/attestation/attestation-agent/agent/src/restapi/mod.rs +new file mode 100644 +index 0000000..490242a +--- /dev/null ++++ b/service/attestation/attestation-agent/agent/src/restapi/mod.rs +@@ -0,0 +1,140 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use attestation_agent::{AttestationAgent, AttestationAgentAPIs, TokenRequest}; ++use attestation_agent::result::Result; ++ ++use actix_web::{ post, get, web, HttpResponse}; ++use attester::EvidenceRequest; ++use serde::{Deserialize, Serialize}; ++use std::sync::Arc; ++use tokio::sync::RwLock; ++use log; ++use base64_url; ++ ++#[derive(Deserialize, Serialize, Debug)] ++struct GetChallengeRequest {} ++ ++#[get("/challenge")] ++pub async fn get_challenge( ++ //_request: web::Json, ++ agent: web::Data>>, ++) -> Result { ++ //let request = request.0; ++ log::debug!("get challenge request"); ++ let challenge = agent.read().await.get_challenge().await?; ++ ++ Ok(HttpResponse::Ok().body(challenge)) ++} ++ ++#[derive(Deserialize, Serialize, Debug)] ++struct GetEvidenceRequest { ++ challenge: String, ++ uuid: String, ++ ima: Option, ++} ++ ++#[get("/evidence")] ++pub async fn get_evidence( ++ request: web::Json, ++ agent: web::Data>>, ++) -> Result { ++ let request = request.0; ++ log::debug!("get evidence request: {:?}", request); ++ let challenge = base64_url::decode(&request.challenge).expect("base64 decode challenge"); ++ let uuid = request.uuid; ++ let ima = request.ima; ++ let input = EvidenceRequest { ++ uuid: uuid, ++ challenge: challenge, ++ ima: ima, ++ }; ++ let evidence = agent.read().await.get_evidence(input).await?; ++ ++ ++ Ok(HttpResponse::Ok().body(evidence)) ++} ++ ++#[derive(Deserialize, Serialize, Debug)] ++struct VerifyEvidenceRequest { ++ challenge: String, ++ evidence: String, ++ policy_id: Option>, ++} ++#[post("/evidence")] ++pub async fn verify_evidence( ++ request: web::Json, ++ agent: web::Data>>, ++) -> Result { ++ let request = request.0; ++ log::debug!("verify evidence request: {:?}", request); ++ let challenge = base64_url::decode(&request.challenge).expect("base64 decode challenge"); ++ let evidence = request.evidence; ++ let policy_id = request.policy_id; ++ ++ let claim = agent.read().await.verify_evidence(&challenge, evidence.as_bytes(), policy_id).await?; ++ let string_claim = serde_json::to_string(&claim)?; ++ ++ Ok(HttpResponse::Ok().body(string_claim)) ++} ++ ++#[derive(Deserialize, Serialize, Debug)] ++struct GetTokenRequest { ++ challenge: String, ++ uuid: String, ++ ima: Option, ++ policy_id: Option>, ++} ++ ++#[get("/token")] ++pub async fn get_token( ++ request: web::Json, ++ agent: web::Data>>, ++) -> Result { ++ let request = request.0; ++ log::debug!("get token request: {:?}", request); ++ let challenge = base64_url::decode(&request.challenge).expect("base64 decode challenge"); ++ let uuid = request.uuid; ++ let ima = request.ima; ++ let policy_id = request.policy_id; ++ let ev = EvidenceRequest { ++ uuid: uuid, ++ challenge: challenge, ++ ima: ima, ++ }; ++ let input = TokenRequest { ++ ev_req: ev, ++ policy_id: policy_id, ++ }; ++ ++ let token = agent.read().await.get_token(input).await?; ++ ++ ++ Ok(HttpResponse::Ok().body(token)) ++} ++ ++#[derive(Deserialize, Serialize, Debug)] ++struct VerifyTokenRequest { ++ token: String, ++} ++#[post("/token")] ++pub async fn verify_token( ++ request: web::Json, ++ agent: web::Data>>, ++) -> Result { ++ let request = request.0; ++ log::debug!("verify token request: {:?}", request); ++ ++ let claim = agent.read().await.verify_token(request.token).await?; ++ let string_claim = serde_json::to_string(&claim)?; ++ ++ Ok(HttpResponse::Ok().body(string_claim)) ++} +\ No newline at end of file +diff --git a/service/attestation/attestation-agent/agent/src/result/mod.rs b/service/attestation/attestation-agent/agent/src/result/mod.rs +new file mode 100644 +index 0000000..f06f064 +--- /dev/null ++++ b/service/attestation/attestation-agent/agent/src/result/mod.rs +@@ -0,0 +1,50 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use actix_web::{body::BoxBody, HttpResponse, ResponseError}; ++ ++pub type Result = std::result::Result; ++ ++/// libdevice error ++#[derive(Debug, thiserror::Error)] ++#[non_exhaustive] ++#[allow(missing_docs)] ++pub enum Error { ++ #[error("IO error: {source:?}")] ++ Io { ++ #[from] ++ source: std::io::Error, ++ }, ++ ++ #[error("Web error: {source:?}")] ++ Web { ++ #[from] ++ source: actix_web::error::Error, ++ }, ++ ++ #[error("Deserialize error: {source:?}")] ++ Deserialize { ++ #[from] ++ source: serde_json::Error, ++ }, ++ ++ #[error("Attestation Agent error:{0}")] ++ AttestationAgentError(String), ++ ++ #[error(transparent)] ++ Other(#[from] anyhow::Error), ++} ++ ++impl ResponseError for Error { ++ fn error_response(&self) -> actix_web::HttpResponse { ++ HttpResponse::InternalServerError().body(BoxBody::new(format!("{self:#?}"))) ++ } ++} +diff --git a/service/attestation/attestation-agent/attester/Cargo.toml b/service/attestation/attestation-agent/attester/Cargo.toml +new file mode 100644 +index 0000000..a7dae2a +--- /dev/null ++++ b/service/attestation/attestation-agent/attester/Cargo.toml +@@ -0,0 +1,18 @@ ++[package] ++name = "attester" ++version = "0.1.0" ++edition = "2021" ++ ++[features] ++itrustee-attester = [ "base64-url", "rand" ] ++virtcca-attester = [] ++ ++[dependencies] ++anyhow.workspace = true ++serde.workspace = true ++serde_json.workspace = true ++rand = { workspace = true, optional = true } ++base64-url = { workspace = true, optional = true } ++async-trait.workspace = true ++log.workspace = true ++attestation-types.workspace = true +diff --git a/service/attestation/attestation-agent/attester/src/itrustee/itrustee.rs b/service/attestation/attestation-agent/attester/src/itrustee/itrustee.rs +new file mode 100644 +index 0000000..9a711c2 +--- /dev/null ++++ b/service/attestation/attestation-agent/attester/src/itrustee/itrustee.rs +@@ -0,0 +1,51 @@ ++/* automatically generated by rust-bindgen 0.69.4 */ ++ ++#[repr(C)] ++#[derive(Debug, Copy, Clone)] ++pub struct ra_buffer_data { ++ pub size: ::std::os::raw::c_uint, ++ pub buf: *mut ::std::os::raw::c_uchar, ++} ++#[test] ++fn bindgen_test_layout_ra_buffer_data() { ++ const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); ++ let ptr = UNINIT.as_ptr(); ++ assert_eq!( ++ ::std::mem::size_of::(), ++ 16usize, ++ concat!("Size of: ", stringify!(ra_buffer_data)) ++ ); ++ assert_eq!( ++ ::std::mem::align_of::(), ++ 8usize, ++ concat!("Alignment of ", stringify!(ra_buffer_data)) ++ ); ++ assert_eq!( ++ unsafe { ::std::ptr::addr_of!((*ptr).size) as usize - ptr as usize }, ++ 0usize, ++ concat!( ++ "Offset of field: ", ++ stringify!(ra_buffer_data), ++ "::", ++ stringify!(size) ++ ) ++ ); ++ assert_eq!( ++ unsafe { ::std::ptr::addr_of!((*ptr).buf) as usize - ptr as usize }, ++ 8usize, ++ concat!( ++ "Offset of field: ", ++ stringify!(ra_buffer_data), ++ "::", ++ stringify!(buf) ++ ) ++ ); ++} ++ ++#[link(name = "qca")] ++extern "C" { ++ pub fn RemoteAttest( ++ in_: *mut ra_buffer_data, ++ out: *mut ra_buffer_data, ++ ) -> ::std::os::raw::c_uint; ++} +diff --git a/service/attestation/attestation-agent/attester/src/itrustee/mod.rs b/service/attestation/attestation-agent/attester/src/itrustee/mod.rs +new file mode 100644 +index 0000000..3fde5f7 +--- /dev/null ++++ b/service/attestation/attestation-agent/attester/src/itrustee/mod.rs +@@ -0,0 +1,130 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++//! itrustee tee plugin ++//! ++//! Call the hardware sdk or driver to get the specific evidence ++ ++use anyhow::*; ++use serde_json; ++use std::path::Path; ++use serde::{Serialize, Deserialize}; ++use base64_url; ++use log; ++ ++use crate::EvidenceRequest; ++ ++mod itrustee; ++ ++#[derive(Debug, Default)] ++pub struct ItrusteeAttester {} ++ ++impl ItrusteeAttester { ++ pub async fn tee_get_evidence(&self, user_data: EvidenceRequest) -> Result { ++ let ret = itrustee_provision(); ++ if ret.is_err() { ++ log::error!("itrustee attester provision failed"); ++ bail!("itrustee attester provision failed"); ++ } ++ ++ itrustee_get_evidence(user_data) ++ } ++} ++ ++pub fn detect_platform() -> bool { ++ Path::new("/usr/bin/tee").exists() ++} ++ ++#[derive(Serialize, Deserialize)] ++struct ReportInputPayload { ++ version: String, ++ nonce: String, ++ uuid: String, ++ hash_alg: String, ++ with_tcb: bool, ++ request_key: bool, ++} ++ ++#[derive(Serialize, Deserialize)] ++struct ItrusteeInput { ++ handler: String, ++ payload: ReportInputPayload, ++} ++ ++fn itrustee_get_evidence(user_data: EvidenceRequest) -> Result { ++ let payload = ReportInputPayload { ++ nonce: base64_url::encode(&user_data.challenge), ++ uuid: user_data.uuid, ++ with_tcb: false, ++ request_key: true, ++ version: String::from("TEE.RA.1.0"), ++ hash_alg: String::from("HS256"), ++ }; ++ ++ let itrustee_input: ItrusteeInput = ItrusteeInput { ++ handler: String::from("report-input"), ++ payload: payload, ++ }; ++ let mut buf = serde_json::to_string(&itrustee_input)?; ++ let mut input = itrustee::ra_buffer_data { ++ size: buf.len() as ::std::os::raw::c_uint, ++ buf: buf.as_mut_ptr() as *mut ::std::os::raw::c_uchar, ++ }; ++ ++ let mut report = Vec::new(); ++ report.resize(0x3000, b'\0'); ++ let mut output = itrustee::ra_buffer_data { ++ size: report.len() as ::std::os::raw::c_uint, ++ buf: report.as_mut_ptr() as *mut ::std::os::raw::c_uchar, ++ }; ++ ++ unsafe { ++ let ret = itrustee::RemoteAttest(&mut input, &mut output); ++ if ret != 0 { ++ log::error!("itrustee get report failed, ret:{}", ret); ++ bail!("itrustee get report failed, ret:{}", ret); ++ } ++ let out_len: usize = output.size.try_into()?; ++ report.set_len(out_len); ++ } ++ let str_report = String::from_utf8(report)?; ++ ++ Ok(str_report) ++} ++ ++fn itrustee_provision() -> Result<()> { ++ let json = r#"{"handler":"provisioning-input","payload":{"version":"TEE.RA.1.0","scenario":"sce_no_as","hash_alg":"HS256"}}"#; ++ ++ let provision_input: serde_json::Value = serde_json::from_str(json)?; ++ let mut provision_input = provision_input.to_string(); ++ ++ let mut input = itrustee::ra_buffer_data { ++ size: provision_input.len() as ::std::os::raw::c_uint, ++ buf: provision_input.as_mut_ptr() as *mut ::std::os::raw::c_uchar, ++ }; ++ ++ let mut report = Vec::new(); ++ report.resize(0x3000, b'\0'); ++ ++ let mut output = itrustee::ra_buffer_data { ++ size: report.len() as ::std::os::raw::c_uint, ++ buf: report.as_mut_ptr() as *mut ::std::os::raw::c_uchar, ++ }; ++ unsafe { ++ let ret = itrustee::RemoteAttest(&mut input, &mut output); ++ if ret != 0 { ++ log::error!("itrustee provision failed, ret:{}", ret); ++ bail!("itrustee provision failed, ret:{}", ret); ++ } ++ } ++ Ok(()) ++} +diff --git a/service/attestation/attestation-agent/attester/src/lib.rs b/service/attestation/attestation-agent/attester/src/lib.rs +new file mode 100644 +index 0000000..3c02946 +--- /dev/null ++++ b/service/attestation/attestation-agent/attester/src/lib.rs +@@ -0,0 +1,79 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++//! attester ++//! ++//! This crate provides unified APIs to get TEE evidence. ++ ++use anyhow::*; ++use async_trait::async_trait; ++use log; ++use attestation_types::{TeeType, Evidence}; ++ ++#[cfg(feature = "itrustee-attester")] ++mod itrustee; ++ ++#[cfg(feature = "virtcca-attester")] ++pub mod virtcca; ++ ++#[derive(Debug, Clone)] ++pub struct EvidenceRequest { ++ pub uuid: String, ++ pub challenge: Vec, ++ pub ima: Option, ++} ++ ++#[async_trait] ++pub trait AttesterAPIs { ++ /// Call tee plugin to get the hardware evidence. ++ /// Automatically detect the TEE type of the current running environment. ++ async fn tee_get_evidence(&self, user_data: EvidenceRequest) -> Result>; ++} ++ ++#[derive(Default)] ++pub struct Attester {} ++ ++ ++const MAX_CHALLENGE_LEN: usize = 64; ++ ++#[async_trait] ++impl AttesterAPIs for Attester { ++ async fn tee_get_evidence(&self, _user_data: EvidenceRequest) -> Result> { ++ let len = _user_data.challenge.len(); ++ if len <= 0 || len > MAX_CHALLENGE_LEN { ++ log::error!("challenge len is error, expecting 0 < len <= {}, got {}", MAX_CHALLENGE_LEN, len); ++ bail!("challenge len is error, expecting 0 < len <= {}, got {}", MAX_CHALLENGE_LEN, len); ++ } ++ #[cfg(feature = "itrustee-attester")] ++ if itrustee::detect_platform() { ++ let evidence = itrustee::ItrusteeAttester::default().tee_get_evidence(_user_data).await?; ++ let aa_evidence = Evidence { ++ tee: TeeType::Itrustee, ++ evidence: evidence, ++ }; ++ let evidence = serde_json::to_vec(&aa_evidence)?; ++ ++ return Ok(evidence); ++ } ++ #[cfg(feature = "virtcca-attester")] ++ if virtcca::detect_platform() { ++ let evidence = virtcca::VirtccaAttester::default().tee_get_evidence(_user_data).await?; ++ let aa_evidence = Evidence { ++ tee: TeeType::Virtcca, ++ evidence: evidence, ++ }; ++ let evidence = serde_json::to_vec(&aa_evidence)?; ++ return Ok(evidence); ++ } ++ bail!("unkown tee platform"); ++ } ++} +\ No newline at end of file +diff --git a/service/attestation/attestation-agent/attester/src/virtcca/mod.rs b/service/attestation/attestation-agent/attester/src/virtcca/mod.rs +new file mode 100644 +index 0000000..c981d91 +--- /dev/null ++++ b/service/attestation/attestation-agent/attester/src/virtcca/mod.rs +@@ -0,0 +1,93 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++//! virtcca tee plugin ++//! ++//! Call the hardware sdk or driver to get the specific evidence ++ ++use anyhow::{Result, bail}; ++use std::path::Path; ++use log; ++use attestation_types::VirtccaEvidence; ++ ++use crate::EvidenceRequest; ++use crate::virtcca::virtcca::tsi_free_ctx; ++use self::virtcca::{tsi_new_ctx, get_attestation_token, get_dev_cert}; ++ ++mod virtcca; ++ ++#[derive(Debug, Default)] ++pub struct VirtccaAttester {} ++ ++ ++impl VirtccaAttester { ++ pub async fn tee_get_evidence(&self, user_data: EvidenceRequest) -> Result { ++ let evidence = virtcca_get_token(user_data)?; ++ let evidence = serde_json::to_string(&evidence)?; ++ Ok(evidence) ++ } ++} ++ ++pub fn detect_platform() -> bool { ++ Path::new("/dev/tsi").exists() ++} ++ ++ ++fn virtcca_get_token(user_data: EvidenceRequest) -> Result { ++ unsafe { ++ let ctx = tsi_new_ctx(); ++ ++ let mut challenge = user_data.challenge.to_vec(); ++ let p_challenge = challenge.as_mut_ptr() as *mut ::std::os::raw::c_uchar; ++ let challenge_len = challenge.len() as usize; ++ let mut token = Vec::new(); ++ token.resize(4096, b'\0'); ++ let p_token = token.as_mut_ptr() as *mut ::std::os::raw::c_uchar; ++ let mut token_len = token.len(); ++ let p_token_len = &mut token_len as *mut usize; ++ let ret = get_attestation_token(ctx, p_challenge, challenge_len, p_token, p_token_len); ++ if ret != 0 { ++ log::error!("virtcca get attestation token failed {}", ret); ++ bail!("virtcca get attestation token failed {}", ret); ++ } ++ token.set_len(token_len); ++ ++ let mut dev_cert = Vec::new(); ++ dev_cert.resize(4096, b'\0'); ++ let p_dev_cert = dev_cert.as_mut_ptr() as *mut ::std::os::raw::c_uchar; ++ let mut dev_cert_len = dev_cert.len(); ++ let p_dev_cert_len = &mut dev_cert_len as *mut usize; ++ let ret = get_dev_cert(ctx, p_dev_cert, p_dev_cert_len); ++ if ret != 0 { ++ log::error!("get dev cert failed {}", ret); ++ bail!("get dev cert failed {}", ret); ++ } ++ dev_cert.set_len(dev_cert_len); ++ ++ let with_ima = match user_data.ima { ++ Some(ima) => ima, ++ None => false, ++ }; ++ let ima_log = match with_ima { ++ true => Some(std::fs::read("/sys/kernel/security/ima/binary_runtime_measurements").unwrap()), ++ false => None, ++ }; ++ ++ let evidence = VirtccaEvidence { ++ evidence: token, ++ dev_cert: dev_cert, ++ ima_log: ima_log, ++ }; ++ let _ = tsi_free_ctx(ctx); ++ Ok(evidence) ++ } ++} +\ No newline at end of file +diff --git a/service/attestation/attestation-agent/attester/src/virtcca/virtcca.rs b/service/attestation/attestation-agent/attester/src/virtcca/virtcca.rs +new file mode 100644 +index 0000000..33318c7 +--- /dev/null ++++ b/service/attestation/attestation-agent/attester/src/virtcca/virtcca.rs +@@ -0,0 +1,109 @@ ++/* automatically generated by rust-bindgen 0.69.4 */ ++#[allow(non_camel_case_types)] ++pub type wchar_t = ::std::os::raw::c_int; ++#[allow(dead_code)] ++#[repr(C)] ++#[repr(align(16))] ++#[derive(Debug, Copy, Clone)] ++pub struct max_align_t { ++ pub __clang_max_align_nonce1: ::std::os::raw::c_longlong, ++ pub __bindgen_padding_0: u64, ++ pub __clang_max_align_nonce2: u128, ++} ++#[test] ++fn bindgen_test_layout_max_align_t() { ++ const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); ++ let ptr = UNINIT.as_ptr(); ++ assert_eq!( ++ ::std::mem::size_of::(), ++ 32usize, ++ concat!("Size of: ", stringify!(max_align_t)) ++ ); ++ assert_eq!( ++ ::std::mem::align_of::(), ++ 16usize, ++ concat!("Alignment of ", stringify!(max_align_t)) ++ ); ++ assert_eq!( ++ unsafe { ::std::ptr::addr_of!((*ptr).__clang_max_align_nonce1) as usize - ptr as usize }, ++ 0usize, ++ concat!( ++ "Offset of field: ", ++ stringify!(max_align_t), ++ "::", ++ stringify!(__clang_max_align_nonce1) ++ ) ++ ); ++ assert_eq!( ++ unsafe { ::std::ptr::addr_of!((*ptr).__clang_max_align_nonce2) as usize - ptr as usize }, ++ 16usize, ++ concat!( ++ "Offset of field: ", ++ stringify!(max_align_t), ++ "::", ++ stringify!(__clang_max_align_nonce2) ++ ) ++ ); ++} ++#[repr(C)] ++#[derive(Debug, Copy, Clone)] ++pub struct tsi_ctx { ++ pub fd: wchar_t, ++} ++#[test] ++fn bindgen_test_layout_tsi_ctx() { ++ const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); ++ let ptr = UNINIT.as_ptr(); ++ assert_eq!( ++ ::std::mem::size_of::(), ++ 4usize, ++ concat!("Size of: ", stringify!(tsi_ctx)) ++ ); ++ assert_eq!( ++ ::std::mem::align_of::(), ++ 4usize, ++ concat!("Alignment of ", stringify!(tsi_ctx)) ++ ); ++ assert_eq!( ++ unsafe { ::std::ptr::addr_of!((*ptr).fd) as usize - ptr as usize }, ++ 0usize, ++ concat!( ++ "Offset of field: ", ++ stringify!(tsi_ctx), ++ "::", ++ stringify!(fd) ++ ) ++ ); ++} ++ ++#[link(name = "vccaattestation")] ++extern "C" { ++ pub fn tsi_new_ctx() -> *mut tsi_ctx; ++} ++extern "C" { ++ pub fn tsi_free_ctx(ctx: *mut tsi_ctx); ++} ++extern "C" { ++ #[allow(dead_code)] ++ pub fn get_version( ++ ctx: *mut tsi_ctx, ++ major: *mut wchar_t, ++ minor: *mut wchar_t, ++ ) -> wchar_t; ++} ++extern "C" { ++ pub fn get_attestation_token( ++ ctx: *mut tsi_ctx, ++ challenge: *mut ::std::os::raw::c_uchar, ++ challenge_len: usize, ++ token: *mut ::std::os::raw::c_uchar, ++ token_len: *mut usize, ++ ) -> wchar_t; ++} ++extern "C" { ++ pub fn get_dev_cert( ++ ctx: *mut tsi_ctx, ++ dev_cert: *mut ::std::os::raw::c_uchar, ++ dev_cert_len: *mut usize, ++ ) -> wchar_t; ++} +diff --git a/service/attestation/attestation-agent/token/Cargo.toml b/service/attestation/attestation-agent/token/Cargo.toml +new file mode 100644 +index 0000000..aa5cafc +--- /dev/null ++++ b/service/attestation/attestation-agent/token/Cargo.toml +@@ -0,0 +1,13 @@ ++[package] ++name = "token_verifier" ++version = "0.1.0" ++edition = "2021" ++ ++# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html ++ ++[dependencies] ++jsonwebtoken.workspace = true ++serde.workspace = true ++serde_json.workspace = true ++anyhow.workspace = true ++attestation-types.workspace = true +\ No newline at end of file +diff --git a/service/attestation/attestation-agent/token/src/lib.rs b/service/attestation/attestation-agent/token/src/lib.rs +new file mode 100644 +index 0000000..50a7a7a +--- /dev/null ++++ b/service/attestation/attestation-agent/token/src/lib.rs +@@ -0,0 +1,114 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use anyhow::{Result, bail}; ++use std::path::Path; ++use serde::{Deserialize, Serialize}; ++use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation }; ++use attestation_types::Claims; ++ ++#[derive(Clone, Debug, Serialize, Deserialize)] ++pub struct TokenVerifyConfig { ++ pub cert: String, // Attestation Service cert to verify jwt token signature ++ pub iss: String, // Attestation Service name ++ //pub root_cert: String, ++} ++ ++impl Default for TokenVerifyConfig { ++ fn default() -> Self { ++ TokenVerifyConfig { ++ cert: "/etc/attestation/attestation-agent/as_cert.pem".to_string(), ++ iss: "oeas".to_string(), ++ } ++ } ++} ++pub struct TokenVerifier ++{ ++ pub config: TokenVerifyConfig, ++} ++ ++impl Default for TokenVerifier ++{ ++ fn default() -> Self { ++ TokenVerifier { ++ config: TokenVerifyConfig::default(), ++ } ++ } ++} ++ ++// 返回token的原始数据 ++#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] ++pub struct TokenRawData { ++ pub header: String, ++ pub claim: String, ++} ++ ++impl TokenVerifier { ++ pub fn new(config: TokenVerifyConfig) -> Result { ++ Ok(TokenVerifier { config }) ++ } ++ fn support_rs(alg: &Algorithm) -> bool ++ { ++ if *alg == Algorithm::RS256 || *alg == Algorithm::RS384 || *alg == Algorithm::RS512{ ++ return true; ++ } ++ return false; ++ } ++ fn support_ps(alg: &Algorithm) -> bool ++ { ++ if *alg == Algorithm::PS256 || *alg == Algorithm::PS384 || *alg == Algorithm::PS512 { ++ return true; ++ } ++ return false; ++ } ++ pub fn verify( ++ &self, ++ token: &String ++ ) -> Result { ++ let header = match decode_header(&token) { ++ Ok(h) => h, ++ Err(e) => bail!("decode jwt header error {:?}", e), ++ }; ++ let alg: Algorithm = header.alg; ++ ++ if !Self::support_rs(&alg) && !Self::support_ps(&alg) { ++ bail!("unknown algrithm {:?}", alg); ++ } ++ if !Path::new(&self.config.cert).exists() { ++ bail!("token verfify failed, {:?} cert not exist", self.config.cert); ++ } ++ let cert = std::fs::read(&self.config.cert).unwrap(); ++ ++ /* 使用配置的公钥 */ ++ let key_value: DecodingKey = match DecodingKey::from_rsa_pem(&cert) ++ { ++ Ok(key) => key, ++ Err(e) => bail!("get key from pem error {:?}", e), ++ }; ++ ++ let mut validation = Validation::new(alg); ++ validation.set_issuer(&[self.config.iss.clone()]); ++ validation.validate_exp = true; ++ ++ let data = decode::(&token, &key_value, &validation); ++ match data { ++ Ok(d) => { ++ let header = d.header.clone(); ++ let claims = d.claims.clone(); ++ Ok(TokenRawData { ++ header: serde_json::to_string(&header).unwrap(), ++ claim: serde_json::to_string(&claims).unwrap(), ++ }) ++ } ++ Err(e) => bail!("verfiy jwt failed {:?}", e), ++ } ++ } ++} +diff --git a/service/attestation/attestation-service/Cargo.toml b/service/attestation/attestation-service/Cargo.toml +new file mode 100644 +index 0000000..cf0dd87 +--- /dev/null ++++ b/service/attestation/attestation-service/Cargo.toml +@@ -0,0 +1,42 @@ ++[workspace] ++resolver = "2" ++members = [ ++ "service", ++ "verifier", ++ "token", ++ "reference", ++ "policy", ++ "tests" ++] ++ ++[workspace.dependencies] ++anyhow = "1.0.80" ++serde = "1.0" ++serde_json = "1.0" ++async-trait = "0.1.78" ++cose-rust = "0.1.7" ++ciborium = "0.2.2" ++hex = "0.4" ++openssl = "0.10.64" ++log = "0.4.14" ++futures = "0.3.30" ++rand = "0.8.5" ++ima-measurements = "0.2.0" ++fallible-iterator = "0.2.0" ++ ++actix-web = "4.5" ++env_logger = "0.9" ++tokio = { version = "1", features = ["full"] } ++strum = { version = "0.25", features = ["derive"] } ++thiserror = "1.0" ++base64-url = "3.0.0" ++base64 = "0.22.0" ++jsonwebtoken = "9.3.0" ++clap = { version = "4.5.7", features = ["derive"] } ++regorus = "0.2.2" ++sled = "0.34.7" ++lazy_static = "1.5.0" ++uuid = { version = "1.2.2", features = ["serde", "v4"] } ++scc = "2.1" ++ ++attestation-types = {path = "../attestation-types"} +diff --git a/service/attestation/attestation-service/README.md b/service/attestation/attestation-service/README.md +new file mode 100644 +index 0000000..c64e6f1 +--- /dev/null ++++ b/service/attestation/attestation-service/README.md +@@ -0,0 +1,6 @@ ++# Attestation Service ++The Attestation Service verifies hardware TEE evidence. ++The first phase aims to support Kunpeng Trustzone, virtCCA and QingTian Enclave. In the future, it will support ARM CCA, Intel TDX, Hygon CSV etc. ++ ++# Overview ++TODO +diff --git a/service/attestation/attestation-service/policy/Cargo.toml b/service/attestation/attestation-service/policy/Cargo.toml +new file mode 100644 +index 0000000..87917a4 +--- /dev/null ++++ b/service/attestation/attestation-service/policy/Cargo.toml +@@ -0,0 +1,12 @@ ++[package] ++name = "policy" ++version = "0.1.0" ++edition = "2021" ++ ++# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html ++ ++[dependencies] ++regorus.workspace = true ++base64.workspace = true ++tokio.workspace = true ++futures.workspace = true +diff --git a/service/attestation/attestation-service/policy/src/lib.rs b/service/attestation/attestation-service/policy/src/lib.rs +new file mode 100644 +index 0000000..0677f45 +--- /dev/null ++++ b/service/attestation/attestation-service/policy/src/lib.rs +@@ -0,0 +1,181 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++pub mod opa; ++pub mod policy_engine; ++ ++#[cfg(test)] ++mod tests { ++ use base64::Engine; ++ use std::fs; ++ ++ use crate::{ ++ opa::OPA, ++ policy_engine::{PolicyEngine, PolicyEngineError}, ++ }; ++ ++ #[tokio::test] ++ async fn test_new_policy_engine() { ++ let policy_dir = String::from("/etc/attestation/attestation-service/policy"); ++ let ret = OPA::new(&policy_dir).await; ++ assert!(ret.is_ok()); ++ } ++ ++ #[tokio::test] ++ async fn test_new_policy_engine_dir_exist() { ++ let policy_dir = String::from("/etc/attestation/attestation-service/policy"); ++ let _ = fs::create_dir_all(&policy_dir); ++ let ret = OPA::new(&policy_dir).await; ++ assert!(ret.is_ok()); ++ } ++ ++ #[tokio::test] ++ async fn test_new_policy_engine_dir_failed() { ++ let policy_dir = String::from("/sys/invalid_dir"); ++ let ret = OPA::new(&policy_dir).await; ++ assert!(ret.is_err()); ++ if let PolicyEngineError::CreatePolicyDirError(msg) = ret.err().unwrap() { ++ assert_eq!(msg, "policy dir create failed"); ++ } else { ++ panic!("Unexpected error type"); ++ } ++ } ++ ++ #[tokio::test] ++ async fn test_set_policy() { ++ let policy_dir = String::from("/etc/attestation/attestation-service/policy"); ++ let engine = OPA::new(&policy_dir).await; ++ ++ let policy_id = "test.rego".to_string(); ++ let policy = r#"package attestation ++import rego.v1 ++expect_keys := ["RIM", "RPV"] ++input_keys := object.keys(input) ++output[exist] := input[exist] if { ++ some exist in expect_keys ++ exist in input_keys ++} ++output[exist] := null if { ++ some exist in expect_keys ++ not exist in input_keys ++} ++output["Other"] := "other" if { ++ "test" in input_keys ++}"#; ++ let _ = ++ tokio::fs::remove_file("/etc/attestation/attestation-service/policy/test.rego").await; ++ ++ let ret = engine ++ .unwrap() ++ .set_policy( ++ &policy_id, ++ &base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(policy), ++ ) ++ .await; ++ assert!(ret.is_ok()); ++ } ++ ++ #[tokio::test] ++ async fn test_get_all_policy() { ++ let policy_dir = String::from("/etc/attestation/attestation-service/policy"); ++ let engine = OPA::new(&policy_dir).await; ++ let ret = engine.unwrap().get_all_policy().await; ++ println!("{:?}", ret); ++ assert!(ret.is_ok()); ++ } ++ ++ #[tokio::test] ++ async fn test_evaluate_by_default() { ++ let policy_dir = String::from("/etc/attestation/attestation-service/policy"); ++ let engine = OPA::new(&policy_dir).await.unwrap(); ++ let refs_from_report = String::from( ++ r#"{ ++ "RIM": "7d2e49c8d29f18b748e658e7243ecf26bc292e5fee93f72af11ad9da9810142a", ++ "RPV": "igliurbwjlkfxvr3wk2kqrttyz4gds42h9sdf72dgpcw8lspts1nnmxuvqzeqyq0", ++ "test": "u4eyoqgqsiju43aooetb02j0rymx6ijhhxs5oryj8344x7kehzjrwsi3vi7wqo2y" ++ }"#, ++ ); ++ let data = String::new(); ++ let policy_id: Vec = vec![]; ++ let result = engine.evaluate(&String::from("vcca"), &refs_from_report, &data, &policy_id).await; ++ println!("{:?}", result); ++ assert!(result.is_ok()); ++ match result { ++ Ok(ret) => { ++ for i in ret.keys() { ++ println!("{} : {}", i, ret[i]); ++ } ++ } ++ Err(err) => { ++ println!("{err}"); ++ } ++ } ++ } ++ ++ #[tokio::test] ++ async fn test_evaluate_use_specified_policy() { ++ // 先设置指定的策略 ++ let policy_dir = String::from("/etc/attestation/attestation-service/policy"); ++ let engine = OPA::new(&policy_dir).await.unwrap(); ++ ++ let policy_id = "test.rego".to_string(); ++ // 该策略提取期望的基线值,如果不存在则设置为null;同时包含“test”基线,则将Other设置为"other" ++ let policy = r#"package attestation ++import rego.v1 ++expect_keys := ["RIM", "RPV"] ++input_keys := object.keys(input) ++output[exist] := input[exist] if { ++ some exist in expect_keys ++ exist in input_keys ++} ++output[exist] := null if { ++ some exist in expect_keys ++ not exist in input_keys ++} ++output["Other"] := "other" if { ++ "test" in input_keys ++}"#; ++ // 删除已重复存在的policy ++ let _ = ++ tokio::fs::remove_file("/etc/attestation/attestation-service/policy/test.rego").await; ++ ++ let ret = engine ++ .set_policy( ++ &policy_id, ++ &base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(policy), ++ ) ++ .await; ++ assert!(ret.is_ok()); ++ ++ // 使用自定义的策略进行报告评估 ++ let refs_from_report = String::from( ++ r#"{ ++ "RIM": "7d2e49c8d29f18b748e658e7243ecf26bc292e5fee93f72af11ad9da9810142a", ++ "RPV": "v598upciquf97yngfi4g2k5r9z6pyl1gcudj1vsgpn7v49ad2oafs11m0esdgv7r", ++ "test": "c4ca91mhcxwqi4ka6ysjgl8nn5hhhln9k2n7ppn3zs1jes4aohlflh5krsogqlpz" ++ }"#, ++ ); ++ let data = String::new(); ++ let policy_id: Vec = vec!["test.rego".to_string()]; ++ let result = engine.evaluate(&String::from("vcca"), &refs_from_report, &data, &policy_id).await; ++ assert!(result.is_ok()); ++ match result { ++ Ok(ret) => { ++ for i in ret.keys() { ++ println!("{} : {}", i, ret[i]); ++ } ++ } ++ Err(err) => { ++ println!("{err}"); ++ } ++ } ++ } ++} +diff --git a/service/attestation/attestation-service/policy/src/opa/default_itrustee.rego b/service/attestation/attestation-service/policy/src/opa/default_itrustee.rego +new file mode 100644 +index 0000000..f55449c +--- /dev/null ++++ b/service/attestation/attestation-service/policy/src/opa/default_itrustee.rego +@@ -0,0 +1,10 @@ ++# if create a new rego file, "output" should exist, ++# package name should be "attestation" ++package attestation ++import rego.v1 ++expect_keys := ["itrustee.ta_img", "itrustee.ta_mem"] ++input_keys := object.keys(input) ++output[exist] := input[exist] if { ++ some exist in expect_keys ++ exist in input_keys ++} +diff --git a/service/attestation/attestation-service/policy/src/opa/default_vcca.rego b/service/attestation/attestation-service/policy/src/opa/default_vcca.rego +new file mode 100644 +index 0000000..32229e4 +--- /dev/null ++++ b/service/attestation/attestation-service/policy/src/opa/default_vcca.rego +@@ -0,0 +1,10 @@ ++# if create a new rego file, "output" should exist, ++# package name should be "attestation" ++package attestation ++import rego.v1 ++expect_keys := ["vcca.cvm.rim"] ++input_keys := object.keys(input) ++output[exist] := input[exist] if { ++ some exist in expect_keys ++ exist in input_keys ++} +diff --git a/service/attestation/attestation-service/policy/src/opa/mod.rs b/service/attestation/attestation-service/policy/src/opa/mod.rs +new file mode 100644 +index 0000000..c2e1cdb +--- /dev/null ++++ b/service/attestation/attestation-service/policy/src/opa/mod.rs +@@ -0,0 +1,167 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use base64::Engine; ++use policy_engine::{PolicyEngine, PolicyEngineError}; ++use regorus::Value; ++use std::{collections::HashMap, path::PathBuf}; ++ ++use crate::policy_engine; ++ ++#[derive(Debug, Clone, PartialEq)] ++pub struct OPA { ++ policy_dir: PathBuf, ++ default_policy_dir: PathBuf, ++ default_policy_vcca: String, ++ default_policy_itrustee: String, ++} ++ ++const DEFAULT_POLICY_DIR: &str = "/etc/attestation/attestation-service/policy/"; ++const DEFAULT_VCCA_REGO: &str = "default_vcca.rego"; ++const DEFAULT_ITRUSTEE_REGO: &str = "default_itrustee.rego"; ++ ++impl PolicyEngine for OPA { ++ /// refs comes from report, by using query reference API ++ async fn evaluate( ++ &self, ++ tee: &String, ++ refs: &String, ++ data_for_policy: &String, ++ policy_id: &Vec, ++ ) -> Result, PolicyEngineError> { ++ let mut policy_id_used = policy_id.clone(); ++ let policy_path: PathBuf; ++ if policy_id_used.is_empty() { ++ if tee == "vcca" { ++ policy_id_used.push(String::from(DEFAULT_VCCA_REGO)); ++ } else if tee == "itrustee" { ++ policy_id_used.push(String::from(DEFAULT_ITRUSTEE_REGO)); ++ } else { ++ return Err(PolicyEngineError::TeeTypeUnknown(format!("tee type unknown: {tee}"))); ++ } ++ policy_path = self.default_policy_dir.clone(); ++ } else { ++ policy_path = self.policy_dir.clone(); ++ } ++ ++ let mut result: HashMap = HashMap::new(); ++ for id in policy_id_used { ++ let mut path = policy_path.clone(); ++ path.push(id.clone()); ++ let engine_policy = tokio::fs::read_to_string(path.clone()).await.map_err(|err| { ++ PolicyEngineError::ReadPolicyError(format!("read policy failed: {}", err)) ++ })?; ++ let mut engine = regorus::Engine::new(); ++ engine.add_policy(id.clone(), engine_policy).map_err(|err| { ++ PolicyEngineError::EngineLoadPolicyError(format!("policy load failed: {}", err)) ++ })?; ++ ++ let input = Value::from_json_str(refs).map_err(|err| { ++ PolicyEngineError::InvalidReport(format!("report to Value failed: {}", err)) ++ })?; ++ engine.set_input(input); ++ ++ if !data_for_policy.is_empty() { ++ let data = Value::from_json_str(data_for_policy).map_err(|err| { ++ PolicyEngineError::EngineLoadDataError(format!("data to Value failed: {}", err)) ++ })?; ++ engine.add_data(data).map_err(|err| { ++ PolicyEngineError::EngineLoadDataError(format!("engine add data failed: {}", err)) ++ })?; ++ } ++ ++ let eval = engine ++ .eval_rule(String::from("data.attestation.output")) ++ .map_err(|err| { ++ PolicyEngineError::EngineEvalError(format!("engine eval error:{}", err)) ++ })?; ++ result.insert(id, eval.to_string()); ++ } ++ Ok(result) ++ } ++ async fn set_policy( ++ &self, ++ policy_id: &String, ++ policy: &String, ++ ) -> Result<(), PolicyEngineError> { ++ let raw = base64::engine::general_purpose::URL_SAFE_NO_PAD ++ .decode(policy) ++ .map_err(|err| PolicyEngineError::InvalidPolicy(format!("policy decode failed: {}", err)))?; ++ ++ let mut policy_file: PathBuf = self.policy_dir.clone(); ++ policy_file.push(format!("{}", policy_id)); ++ tokio::fs::write(policy_file.as_path(), &raw) ++ .await ++ .map_err(|err| PolicyEngineError::WritePolicyError(format!("write policy failed: {}", err)))?; ++ Ok(()) ++ } ++ ++ async fn get_all_policy(&self) -> Result, PolicyEngineError> { ++ let mut items = tokio::fs::read_dir(&self.policy_dir.as_path()) ++ .await ++ .map_err(|err| PolicyEngineError::ReadPolicyError(format!("read policy failed: {}", err)))?; ++ let mut policies = HashMap::new(); ++ while let Some(item) = items ++ .next_entry() ++ .await ++ .map_err(|err| PolicyEngineError::ReadPolicyError(format!("read policy failed: {}", err)))? ++ { ++ let path = item.path(); ++ if path.extension().and_then(std::ffi::OsStr::to_str) == Some("rego") { ++ let content: String = ++ tokio::fs::read_to_string(path.clone()).await.map_err(|err| { ++ PolicyEngineError::ReadPolicyError(format!("read policy failed: {}", err)) ++ })?; ++ let name = path ++ .file_stem() ++ .ok_or(PolicyEngineError::ReadPolicyError( ++ "get policy name failed".to_string(), ++ ))? ++ .to_str() ++ .ok_or(PolicyEngineError::ReadPolicyError( ++ "get policy name failed".to_string(), ++ ))?; ++ let content = ++ base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(content.as_bytes()); ++ policies.insert(name.to_string() + ".rego", content); ++ } ++ } ++ return Ok(policies); ++ } ++ ++ async fn get_policy(&self, policy_id: &String) -> Result { ++ let mut policy_file: PathBuf = self.policy_dir.clone(); ++ policy_file.push(format!("{}", policy_id)); ++ let policy = tokio::fs::read(policy_file.as_path()) ++ .await ++ .map_err(|err| PolicyEngineError::ReadPolicyError(format!("read policy failed: {}", err)))?; ++ let policy_base64 = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(policy); ++ Ok(policy_base64) ++ } ++} ++ ++impl OPA { ++ pub async fn new(policy_dir: &String) -> Result { ++ let policy_path = PathBuf::from(policy_dir); ++ if !policy_path.as_path().exists() { ++ std::fs::create_dir_all(&policy_dir).map_err(|err| { ++ PolicyEngineError::CreatePolicyDirError(format!("policy dir create failed: {}", err)) ++ })?; ++ } ++ ++ Ok(OPA { ++ policy_dir: policy_path, ++ default_policy_dir: PathBuf::from(DEFAULT_POLICY_DIR), ++ default_policy_vcca: String::from(DEFAULT_VCCA_REGO), ++ default_policy_itrustee: String::from(DEFAULT_ITRUSTEE_REGO), ++ }) ++ } ++} +diff --git a/service/attestation/attestation-service/policy/src/policy_engine.rs b/service/attestation/attestation-service/policy/src/policy_engine.rs +new file mode 100644 +index 0000000..a03a8cc +--- /dev/null ++++ b/service/attestation/attestation-service/policy/src/policy_engine.rs +@@ -0,0 +1,73 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use std::{collections::HashMap, fmt::Display}; ++#[derive(Debug)] ++pub enum PolicyEngineError { ++ InvalidPolicy(String), ++ InvalidPolicyId(String), ++ InvalidPolicyDir(String), ++ InvalidReport(String), ++ CreatePolicyDirError(String), ++ CreatePolicyError(String), ++ ReadPolicyError(String), ++ WritePolicyError(String), ++ EngineLoadPolicyError(String), ++ EngineLoadDataError(String), ++ EngineEvalError(String), ++ TeeTypeUnknown(String), ++} ++impl Display for PolicyEngineError { ++ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ++ match self { ++ PolicyEngineError::InvalidPolicy(msg) => write!(f, "invalid policy: {}", msg), ++ PolicyEngineError::InvalidPolicyId(msg) => write!(f, "invalid policy id: {}", msg), ++ PolicyEngineError::InvalidReport(msg) => write!(f, "invalid report: {}", msg), ++ PolicyEngineError::CreatePolicyDirError(msg) => { ++ write!(f, "create policy dir error: {}", msg) ++ } ++ PolicyEngineError::CreatePolicyError(msg) => write!(f, "create policy error: {}", msg), ++ PolicyEngineError::ReadPolicyError(msg) => write!(f, "read policy error: {}", msg), ++ PolicyEngineError::InvalidPolicyDir(msg) => write!(f, "invalid policy error: {}", msg), ++ PolicyEngineError::WritePolicyError(msg) => write!(f, "write policy error: {}", msg), ++ PolicyEngineError::EngineLoadPolicyError(msg) => { ++ write!(f, "engine load policy error: {}", msg) ++ } ++ PolicyEngineError::EngineLoadDataError(msg) => { ++ write!(f, "engine read data error: {}", msg) ++ } ++ PolicyEngineError::EngineEvalError(msg) => write!(f, "engine evaluate error: {}", msg), ++ PolicyEngineError::TeeTypeUnknown(msg) => write!(f, "tee type error: {}", msg), ++ } ++ } ++} ++ ++pub trait PolicyEngine { ++ fn evaluate( ++ &self, ++ tee: &String, ++ refs: &String, ++ data_for_policy: &String, ++ policy_id: &Vec, ++ ) -> impl std::future::Future, PolicyEngineError>> + Send; ++ fn set_policy( ++ &self, ++ policy_id: &String, ++ policy: &String, ++ ) -> impl std::future::Future> + Send; ++ fn get_all_policy( ++ &self, ++ ) -> impl std::future::Future, PolicyEngineError>> + Send; ++ fn get_policy( ++ &self, ++ policy_id: &String, ++ ) -> impl std::future::Future> + Send; ++} +diff --git a/service/attestation/attestation-service/reference/Cargo.toml b/service/attestation/attestation-service/reference/Cargo.toml +new file mode 100644 +index 0000000..b36991e +--- /dev/null ++++ b/service/attestation/attestation-service/reference/Cargo.toml +@@ -0,0 +1,16 @@ ++[package] ++name = "reference" ++version = "0.1.0" ++edition = "2021" ++ ++# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html ++ ++[dependencies] ++serde.workspace = true ++serde_json.workspace = true ++rand.workspace = true ++base64.workspace = true ++sled.workspace = true ++openssl.workspace = true ++hex.workspace = true ++lazy_static.workspace = true +\ No newline at end of file +diff --git a/service/attestation/attestation-service/reference/src/extractor/mod.rs b/service/attestation/attestation-service/reference/src/extractor/mod.rs +new file mode 100644 +index 0000000..41f61aa +--- /dev/null ++++ b/service/attestation/attestation-service/reference/src/extractor/mod.rs +@@ -0,0 +1,30 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use crate::reference::Ref; ++use serde_json::Value; ++pub struct Extractor {} ++impl Extractor { ++ pub fn split(ref_set: &String) -> Option> { ++ // expect ref_set as a json string, like follow: ++ // {"refname1":xx,"refname2":yy} ++ let mut ret: Vec = vec![]; ++ let refs: Value = serde_json::from_str(ref_set.as_str()).ok()?; ++ for (key, val) in refs.as_object().unwrap() { ++ let ref_obj = Ref { ++ name: key.clone(), ++ value: val.clone(), ++ }; ++ ret.push(ref_obj); ++ } ++ Some(ret) ++ } ++} +diff --git a/service/attestation/attestation-service/reference/src/lib.rs b/service/attestation/attestation-service/reference/src/lib.rs +new file mode 100644 +index 0000000..4347fc1 +--- /dev/null ++++ b/service/attestation/attestation-service/reference/src/lib.rs +@@ -0,0 +1,141 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++pub mod local_fs; ++pub mod reference; ++pub mod store; ++mod extractor; ++ ++#[cfg(test)] ++mod tests { ++ use std::thread; ++ ++ use super::*; ++ use rand::{distributions::Alphanumeric, Rng}; ++ use serde_json::Value; ++ ++ #[test] ++ fn localfs_default_test() { ++ let mut ops_default = reference::ReferenceOps::default(); ++ let refs = r#"{"test1":"hash1","test2":"hash2"}"#.to_string(); ++ assert_eq!(ops_default.register(&refs), Ok(())); ++ let ref_query = ops_default.query(&refs).unwrap(); ++ println!("ref:{refs}, query:{ref_query}"); ++ assert_eq!(ref_query, refs); ++ } ++ ++ #[test] ++ fn localfs_empty_reference_test() { ++ let mut ops_default = reference::ReferenceOps::default(); ++ assert_ne!(ops_default.register(&r#""#.to_string()), Ok(())); ++ assert_eq!(ops_default.query(&r#""#.to_string()), None); ++ ++ let refs = r#"{}"#.to_string(); ++ assert_eq!(ops_default.register(&refs), Ok(())); ++ let ref_query = ops_default.query(&refs).unwrap(); ++ println!("ref:{refs}, query:{ref_query}"); ++ assert_eq!(ref_query, refs); ++ } ++ ++ #[test] ++ fn localfs_query_fail_test() { ++ let mut ops_default = reference::ReferenceOps::default(); ++ let refs = r#"{"test1":"hash1"}"#.to_string(); ++ assert_eq!(ops_default.register(&refs), Ok(())); ++ let ref_query = ops_default ++ .query(&r#"{"test":"hash1"}"#.to_string()) ++ .unwrap(); ++ println!("ref:{refs}, query:{ref_query}"); ++ assert_ne!(ref_query, refs); ++ } ++ ++ #[test] ++ fn localfs_default_complex_reference_test() { ++ let mut ops_default = reference::ReferenceOps::default(); ++ let refs = r#"{"test1": { "name1":123, "name2": "val2"},"test2":123}"#.to_string(); ++ assert_eq!(ops_default.register(&refs), Ok(())); ++ let ref_query = ops_default.query(&refs).unwrap(); ++ let json_obj: Value = serde_json::from_str(refs.as_str()).unwrap(); ++ println!("ref:{}, query:{ref_query}", json_obj.to_string()); ++ assert_eq!(ref_query, json_obj.to_string()); ++ } ++ ++ #[test] ++ fn localfs_new_test() { ++ let store = local_fs::LocalFs::new(&String::from("/var/attestation/data_new")); ++ let mut ops = reference::ReferenceOps::new(store); ++ let refs = r#"{"test1":"hash1","test2":"hash2"}"#.to_string(); ++ assert_eq!(ops.register(&refs), Ok(())); ++ let ref_query = ops.query(&refs).unwrap(); ++ println!("ref:{refs}, query:{ref_query}"); ++ assert_eq!(ref_query, refs); ++ } ++ ++ #[test] ++ fn localfs_register_reference_repeat_test() { ++ let mut ops_default = reference::ReferenceOps::default(); ++ let refs = r#"{"test1":"hash1","test2":"hash2"}"#.to_string(); ++ assert_eq!(ops_default.register(&refs), Ok(())); ++ assert_eq!(ops_default.register(&refs), Ok(())); ++ let ref_query = ops_default.query(&refs).unwrap(); ++ println!("ref:{refs}, query:{ref_query}"); ++ assert_eq!(ref_query, refs); ++ } ++ ++ #[test] ++ fn localfs_unregister_reference_test() { ++ let mut ops_default = reference::ReferenceOps::default(); ++ let refs = r#"{"name1":"hash1","name2":"hash2"}"#.to_string(); ++ assert_eq!(ops_default.register(&refs), Ok(())); ++ let ref_query = ops_default.query(&refs).unwrap(); ++ println!("ref:{refs}, query:{ref_query}"); ++ assert_eq!(ref_query, refs); ++ ++ assert_eq!(ops_default.unregister(&refs), Ok(())); ++ let ref_query = ops_default.query(&refs).unwrap(); ++ println!("ref:{refs}, query:{ref_query}"); ++ assert_ne!(refs, ref_query); ++ } ++ ++ #[test] ++ fn localfs_register_query_concurrently() { ++ let mut thread_all = vec![]; ++ let thread_cnt = 1000; ++ for i in 0..thread_cnt { ++ let seq_start = i * thread_cnt; ++ let seq_end = seq_start + thread_cnt; ++ thread_all.push(thread::spawn(move || { ++ let rng = rand::thread_rng(); ++ let mut ops_default = reference::ReferenceOps::default(); ++ ++ for i in seq_start..seq_end { ++ //key ++ let key = format!("ref{}", i); ++ //value ++ let value:String = rng.clone().sample_iter(&Alphanumeric).take(128).map(char::from).collect(); ++ let mut reference = serde_json::json!({}); ++ reference.as_object_mut().unwrap().insert(key, Value::String(value)); ++ let _ = ops_default.register(&reference.to_string()); ++ let ref_query = ops_default.query(&reference.to_string()).unwrap(); ++ println!("ref {} query {}", reference.to_string(), ref_query); ++ assert_eq!(ref_query, reference.to_string()); ++ } ++ })); ++ } ++ for hd in thread_all { ++ match hd.join() { ++ Ok(_) => {} ++ Err(_) => {assert!(false)} ++ } ++ } ++ ++ } ++} +diff --git a/service/attestation/attestation-service/reference/src/local_fs/mod.rs b/service/attestation/attestation-service/reference/src/local_fs/mod.rs +new file mode 100644 +index 0000000..1e03579 +--- /dev/null ++++ b/service/attestation/attestation-service/reference/src/local_fs/mod.rs +@@ -0,0 +1,87 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use lazy_static::lazy_static; ++use std::sync::Arc; ++use sled::Db; ++use std::ops::Deref; ++ ++use crate::store::{KvError, KvStore}; ++ ++ ++pub struct LocalFs { ++ db: Arc, ++} ++ ++impl Default for LocalFs { ++ fn default() -> Self { ++ lazy_static! { ++ static ref db_handle: Arc = Arc::new(sled::open("/etc/attestation/attestation-service/reference").unwrap()); ++ } ++ LocalFs { ++ db: db_handle.clone(), ++ } ++ } ++} ++ ++impl KvStore for LocalFs { ++ fn write(&mut self, key: &str, value: &[u8]) -> Result<(), KvError> { ++ match self.db.insert(key.as_bytes(), value) { ++ Err(_err) => { ++ return Err(KvError::Err("insert error".to_string())); ++ } ++ Ok(_) => {} ++ } ++ match self.db.flush() { ++ Err(_err) => { ++ return Err(KvError::Err("write flush error".to_string())); ++ } ++ Ok(_) => { ++ return Ok(()); ++ } ++ } ++ } ++ fn read(&mut self, key: &str) -> Option> { ++ match self.db.get(key) { ++ Ok(val) => match val { ++ Some(iv) => Some(Vec::from(iv.deref())), ++ None => None, ++ }, ++ Err(_err) => None, ++ } ++ } ++ ++ fn delete(&mut self, key: &str) -> Result<(), KvError> { ++ match self.db.remove(key.as_bytes()) { ++ Err(_err) => { ++ return Err(KvError::Err("delete fail".to_string())); ++ } ++ Ok(_) => (), ++ } ++ match self.db.flush() { ++ Err(_err) => { ++ return Err(KvError::Err("delete flush fail".to_string())); ++ } ++ Ok(_) => { ++ return Ok(()); ++ } ++ } ++ } ++} ++ ++impl LocalFs { ++ pub fn new(path: &String) -> LocalFs { ++ let lfs = LocalFs { ++ db: Arc::new(sled::open(path).unwrap()), ++ }; ++ lfs ++ } ++} +diff --git a/service/attestation/attestation-service/reference/src/reference/mod.rs b/service/attestation/attestation-service/reference/src/reference/mod.rs +new file mode 100644 +index 0000000..bf56c85 +--- /dev/null ++++ b/service/attestation/attestation-service/reference/src/reference/mod.rs +@@ -0,0 +1,147 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use crate::extractor::Extractor; ++use crate::local_fs::LocalFs; ++use crate::store::{KvError, KvStore}; ++use openssl::sha::sha256; ++use serde::{Deserialize, Serialize}; ++use serde_json::{json, Value}; ++ ++pub struct ReferenceOps { ++ store: Box, ++} ++ ++impl Default for ReferenceOps { ++ fn default() -> Self { ++ ReferenceOps { ++ store: Box::new(LocalFs::default()), ++ } ++ } ++} ++#[derive(Debug, Serialize, Deserialize)] ++pub enum HashAlg { ++ SHA256(String), ++ SHA384(String), ++ SHA512(String), ++} ++ ++#[derive(Debug, Serialize, Deserialize)] ++pub struct Ref { ++ pub name: String, ++ pub value: Value, ++} ++ ++#[derive(Debug, PartialEq)] ++pub enum RefOpError { ++ Err(String), ++} ++impl From for RefOpError { ++ fn from(value: KvError) -> Self { ++ match value { ++ KvError::Err(v) => RefOpError::Err(v), ++ } ++ } ++} ++ ++impl ReferenceOps { ++ pub fn new(st: impl KvStore + 'static) -> ReferenceOps { ++ let ops = ReferenceOps { ++ store: Box::new(st), ++ }; ++ ops ++ } ++ ++ fn generate_reference_key(reference: &Ref) -> String { ++ let key = reference.name.clone() + reference.value.to_string().as_str(); ++ hex::encode(sha256(key.as_bytes())) ++ } ++ ++ fn register_reference(&mut self, reference: &Ref) -> Result<(), RefOpError> { ++ // generate reference key ++ let key = Self::generate_reference_key(reference); ++ match self.store.write( ++ &key, ++ serde_json::to_string(&reference) ++ .unwrap() ++ .as_bytes() ++ .as_ref(), ++ ) { ++ Ok(_) => { ++ return Ok(()); ++ } ++ Err(err) => match err { ++ KvError::Err(err) => { ++ return Err(RefOpError::Err(err)); ++ } ++ }, ++ } ++ } ++ ++ fn unregister_reference(&mut self, reference: &Ref) -> Result<(), RefOpError> { ++ let key = Self::generate_reference_key(reference); ++ match self.store.delete(&key) { ++ Ok(_) => { ++ return Ok(()); ++ } ++ Err(err) => match err { ++ KvError::Err(err) => { ++ return Err(RefOpError::Err(err)); ++ } ++ }, ++ } ++ } ++ ++ fn query_reference(&mut self, reference: &Ref) -> Option> { ++ let key = Self::generate_reference_key(reference); ++ self.store.read(&key) ++ } ++ /// ref_set is a json string like:{"refname1":xx,"refname2":yy} ++ pub fn register(&mut self, ref_set: &String) -> Result<(), RefOpError> { ++ let refs = ++ Extractor::split(ref_set).ok_or(RefOpError::Err("parse reference fail".to_string()))?; ++ for item in refs { ++ self.register_reference(&item)? ++ } ++ Ok(()) ++ } ++ ++ pub fn unregister(&mut self, ref_set: &String) -> Result<(), RefOpError> { ++ let refs = ++ Extractor::split(ref_set).ok_or(RefOpError::Err("parse reference fail".to_string()))?; ++ for item in refs { ++ self.unregister_reference(&item)? ++ } ++ Ok(()) ++ } ++ ++ pub fn query(&mut self, ref_set: &String) -> Option { ++ let refs = Extractor::split(ref_set)?; ++ let mut ret: Value = json!({}); ++ for item in refs { ++ // query each reference, reference is set to NULL if not found ++ match self.query_reference(&item) { ++ Some(ref_store) => { ++ let ref_raw: Ref = ++ serde_json::from_str(String::from_utf8(ref_store).unwrap().as_str()) ++ .ok()?; ++ ret.as_object_mut() ++ .unwrap() ++ .insert(ref_raw.name, ref_raw.value); ++ } ++ None => { ++ ret.as_object_mut().unwrap().insert(item.name, Value::Null); ++ } ++ } ++ } ++ Some(ret.to_string()) ++ } ++} +diff --git a/service/attestation/attestation-service/reference/src/store/mod.rs b/service/attestation/attestation-service/reference/src/store/mod.rs +new file mode 100644 +index 0000000..c8c8260 +--- /dev/null ++++ b/service/attestation/attestation-service/reference/src/store/mod.rs +@@ -0,0 +1,19 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++pub enum KvError { ++ Err(String), ++} ++pub trait KvStore { ++ fn write(&mut self, key: &str, value: &[u8]) -> Result<(), KvError>; ++ fn read(&mut self, key: &str) -> Option>; ++ fn delete(&mut self, key: &str) -> Result<(), KvError>; ++} +diff --git a/service/attestation/attestation-service/service/Cargo.toml b/service/attestation/attestation-service/service/Cargo.toml +new file mode 100644 +index 0000000..e8b88b8 +--- /dev/null ++++ b/service/attestation/attestation-service/service/Cargo.toml +@@ -0,0 +1,35 @@ ++[package] ++name = "attestation-service" ++version = "0.1.0" ++edition = "2021" ++ ++[dependencies] ++anyhow.workspace = true ++serde.workspace = true ++hex.workspace = true ++serde_json.workspace = true ++ ++actix-web.workspace = true ++env_logger.workspace = true ++tokio.workspace = true ++log.workspace = true ++base64-url.workspace = true ++base64.workspace = true ++ ++verifier = { path = "../verifier" } ++token_signer = { path = "../token" } ++reference = { path = "../reference" } ++policy = { path = "../policy" } ++strum.workspace = true ++thiserror.workspace = true ++clap.workspace = true ++uuid.workspace = true ++rand.workspace = true ++scc.workspace = true ++attestation-types.workspace = true ++ ++[dev-dependencies] ++futures.workspace = true ++ ++[features] ++ +diff --git a/service/attestation/attestation-service/service/attestation-service.conf b/service/attestation/attestation-service/service/attestation-service.conf +new file mode 100644 +index 0000000..64c5ff0 +--- /dev/null ++++ b/service/attestation/attestation-service/service/attestation-service.conf +@@ -0,0 +1,9 @@ ++{ ++ "token_cfg": { ++ "key": "/etc/attestation/attestation-service/token/private.pem", ++ "iss": "oeas", ++ "nbf": 0, ++ "valid_duration": 300, ++ "alg": "PS256" ++ } ++} +diff --git a/service/attestation/attestation-service/service/src/lib.rs b/service/attestation/attestation-service/service/src/lib.rs +new file mode 100644 +index 0000000..cc3f432 +--- /dev/null ++++ b/service/attestation/attestation-service/service/src/lib.rs +@@ -0,0 +1,204 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use anyhow::{Result, anyhow}; ++use std::fs::File; ++use std::path::Path; ++use std::str::FromStr; ++use serde::{Serialize, Deserialize}; ++use serde_json::Value; ++use rand::RngCore; ++use base64_url; ++ ++use verifier::{Verifier, VerifierAPIs}; ++use token_signer::{EvlReport, TokenSigner, TokenSignConfig}; ++use reference::reference::{ReferenceOps, RefOpError}; ++use policy::opa::OPA; ++use policy::policy_engine::{PolicyEngine, PolicyEngineError}; ++use attestation_types::EvlResult; ++ ++pub mod result; ++#[derive(Clone, Debug, Serialize, Deserialize)] ++pub struct ASConfig { ++ pub token_cfg: TokenSignConfig, ++} ++ ++impl Default for ASConfig { ++ fn default() -> Self { ++ Self { ++ token_cfg: TokenSignConfig::default(), ++ } ++ } ++} ++ ++impl TryFrom<&Path> for ASConfig { ++ /// Load `ASConfig` from a configuration file like: ++ /// { ++ /// "token_cfg": { ++ /// "key": "/etc/attestation/attestation-service/token/private.pem", ++ /// "iss": "oeas", ++ /// "nbf": 0, ++ /// "valid_duration": 300, ++ /// "alg": "PS256" ++ /// } ++ /// } ++ ++ type Error = anyhow::Error; ++ fn try_from(config_path: &Path) -> Result { ++ let file = File::open(config_path)?; ++ serde_json::from_reader::(file).map_err(|e| anyhow!("invalid asconfig {e}")) ++ } ++} ++ ++pub struct AttestationService { ++ pub config: ASConfig, ++ // verify policy sub service ++ //policy: , ++ // reference value provider sub service ++ //rvps: , ++ // tee verifier sub service ++ //verifier: , ++} ++ ++impl Default for AttestationService { ++ fn default() -> Self { ++ Self { ++ config: ASConfig::default(), ++ } ++ } ++} ++ ++impl AttestationService { ++ pub fn new(conf_path: Option) -> Result { ++ let config = match conf_path { ++ Some(conf_path) => { ++ log::info!("Attestation Service config file:{conf_path}"); ++ ASConfig::try_from(Path::new(&conf_path))? ++ } ++ None => { ++ log::warn!("No Attestation Agent config file specified. Using a default config"); ++ ASConfig::default() ++ } ++ }; ++ Ok(AttestationService {config}) ++ } ++ /// evaluate tee evidence with reference and policy, and issue attestation result token ++ pub async fn evaluate( ++ &self, ++ user_data: &[u8], ++ evidence: &[u8], ++ policy_ids: &Option> ++ ) -> Result { ++ let verifier = Verifier::default(); ++ let claims_evidence = verifier.verify_evidence(user_data, evidence).await?; ++ ++ let mut passed = false; ++ let ima_result = verifier.verify_ima(evidence, &claims_evidence).await; ++ if ima_result.is_ok() { ++ passed = true; ++ } ++ // get reference by keys in claims_evidence ++ let mut ops_refs = ReferenceOps::default(); ++ let refs_of_claims = ops_refs.query(&claims_evidence["payload"].to_string()); ++ // apply policy to verify claims_evidence with reference value ++ let policy_ids = match policy_ids { ++ Some(polciy_id) => polciy_id.clone(), ++ None => vec![], ++ }; ++ let policy_dir = String::from("/etc/attestation/attestation-service/policy"); ++ let engine = OPA::new(&policy_dir).await.unwrap(); ++ let data = String::new(); ++ let result = engine.evaluate(&String::from(claims_evidence["tee"] ++ .as_str().ok_or(anyhow!("tee type unknown"))?), ++ &refs_of_claims.unwrap(), &data, &policy_ids).await; ++ let mut report = serde_json::json!({}); ++ let mut ref_exist_null: bool = false; ++ match result { ++ Ok(eval) => { ++ for id in eval.keys() { ++ let val = Value::from_str(&eval[id].clone())?; ++ let refs = match val.as_object().ok_or(Err(anyhow!(""))) { ++ Err(err) => { return Err(err.unwrap()); } ++ Ok(ret) => { ret } ++ }; ++ for key in refs.keys() { ++ // reference value is null means not found ++ if refs[key].is_null() { ++ ref_exist_null = true; ++ } ++ } ++ report.as_object_mut().unwrap().insert(id.clone(), serde_json::Value::String(eval[id].clone())); ++ } ++ } ++ Err(err) => { ++ return Err(anyhow!("evaluate error: {err}")); ++ } ++ } ++ ++ // issue attestation result token ++ let evl_report = EvlReport { ++ tee: String::from(claims_evidence["tee"].as_str().ok_or(anyhow!("tee type unknown"))?), ++ result: EvlResult { ++ eval_result: passed & !ref_exist_null, ++ policy: policy_ids, ++ report: report, ++ }, ++ tcb_status: claims_evidence["payload"].clone(), ++ }; ++ // demo get signer, todo default signer ++ let signer = TokenSigner::new(self.config.token_cfg.clone())?; ++ ++ signer.sign(&evl_report) ++ } ++ ++ pub async fn generate_challenge(&self) -> String { ++ let mut nonce: [u8; 32] = [0; 32]; ++ rand::thread_rng().fill_bytes(&mut nonce); ++ base64_url::encode(&nonce) ++ } ++ ++ // todo pub fun set policy ++ pub async fn set_policy(&self, ++ id: &String, ++ policy: &String, ++ policy_dir: &String, ++ ) -> Result<(), PolicyEngineError> { ++ let engine = OPA::new(policy_dir).await; ++ engine.unwrap() ++ .set_policy(id, policy) ++ .await ++ } ++ // todo pub fun get policy ++ pub async fn get_policy(&self, ++ policy_dir: &String, ++ ) -> Result { ++ let engine = OPA::new(policy_dir).await; ++ match engine.unwrap().get_all_policy().await { ++ Ok(map) => { ++ let mut json_obj: serde_json::Value = serde_json::json!({}); ++ for key in map.keys() { ++ json_obj.as_object_mut() ++ .unwrap() ++ .insert(key.clone(), serde_json::json!(map[key])); ++ } ++ Ok(json_obj.to_string()) ++ } ++ Err(err) => Err(err) ++ } ++ } ++ // todo pub fun import reference value ++ pub async fn register_reference(&self, ++ ref_set: &String ++ ) -> Result<(), RefOpError> { ++ let mut ops_default = ReferenceOps::default(); ++ ops_default.register(ref_set) ++ } ++} +diff --git a/service/attestation/attestation-service/service/src/main.rs b/service/attestation/attestation-service/service/src/main.rs +new file mode 100644 +index 0000000..1ccb152 +--- /dev/null ++++ b/service/attestation/attestation-service/service/src/main.rs +@@ -0,0 +1,76 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++/// RESTful Attestation Service ++ ++use attestation_service::AttestationService; ++mod restapi; ++use restapi::{get_challenge, attestation, reference, get_policy, set_policy}; ++mod session; ++use session::SessionMap; ++ ++use anyhow::Result; ++use env_logger; ++use actix_web::{web, App, HttpServer}; ++use std::{net::{SocketAddr, IpAddr, Ipv4Addr}, sync::Arc}; ++use tokio::sync::RwLock; ++use clap::{Parser, command, arg}; ++ ++const DEFAULT_ASCONFIG_FILE: &str = "/etc/attestation/attestation-service/attestation-service.conf"; ++const DEFAULT_SOCKETADDR: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080); ++ ++#[derive(Parser, Debug)] ++#[command(version, about, long_about = None)] ++struct Cli { ++ /// Socket address to listen on ++ #[arg(short, long, default_value_t = DEFAULT_SOCKETADDR)] ++ socketaddr: SocketAddr, ++ ++ /// Attestation Service config file ++ // Load `ASConfig` from a configuration file like: ++ // { ++ // "token_cfg": { ++ // "key": "/etc/attestation/attestation-service/token/private.pem", ++ // "iss": "oeas", ++ // "nbf": 0, ++ // "valid_duration": 300, ++ // "alg": "PS256" ++ // } ++ // } ++ #[arg(short, long, default_value_t = DEFAULT_ASCONFIG_FILE.to_string())] ++ config: String, ++} ++ ++#[actix_web::main] ++async fn main() -> Result<()> { ++ env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); ++ ++ let cli = Cli::parse(); ++ let server:AttestationService = AttestationService::new(Some(cli.config)).unwrap(); ++ let session_map = web::Data::new(SessionMap::new()); ++ ++ let service = web::Data::new(Arc::new(RwLock::new(server))); ++ HttpServer::new(move || { ++ App::new() ++ .app_data(web::Data::clone(&service)) ++ .app_data(web::Data::clone(&session_map)) ++ .service(get_challenge) ++ .service(attestation) ++ .service(reference) ++ .service(set_policy) ++ .service(get_policy) ++ }) ++ .bind((cli.socketaddr.ip().to_string(), cli.socketaddr.port()))? ++ .run() ++ .await?; ++ ++ Ok(()) ++} +\ No newline at end of file +diff --git a/service/attestation/attestation-service/service/src/restapi/mod.rs b/service/attestation/attestation-service/service/src/restapi/mod.rs +new file mode 100644 +index 0000000..ab2ccbf +--- /dev/null ++++ b/service/attestation/attestation-service/service/src/restapi/mod.rs +@@ -0,0 +1,139 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use attestation_service::AttestationService; ++use attestation_service::result::{Result, Error}; ++use crate::session::{Session, SessionMap}; ++ ++use actix_web::{ post, get, web, HttpResponse, HttpRequest}; ++use serde::{Deserialize, Serialize}; ++use std::sync::Arc; ++use tokio::sync::RwLock; ++use log; ++use base64_url; ++ ++const DEFAULT_POLICY_DIR: &str = "/etc/attestation/attestation-service/policy"; ++#[derive(Deserialize, Serialize, Debug)] ++pub struct ChallengeRequest {} ++ ++#[get("/challenge")] ++pub async fn get_challenge( ++ map: web::Data, ++ service: web::Data>>, ++) -> Result { ++ log::debug!("challenge request"); ++ ++ let challenge = service.read().await.generate_challenge().await; ++ let timeout = service.read().await.config.token_cfg.valid_duration; ++ let session = Session::new(challenge, timeout.try_into().unwrap()); ++ let response = HttpResponse::Ok() ++ .cookie(session.cookie()) ++ .json(session.challenge.clone()); ++ map.insert(session); ++ ++ Ok(response) ++} ++ ++#[derive(Deserialize, Serialize, Debug)] ++pub struct AttestationRequest { ++ challenge: String, ++ evidence: String, ++ policy_id: Option>, ++} ++ ++#[post("/attestation")] ++pub async fn attestation( ++ request: web::Json, ++ http_req: HttpRequest, ++ map: web::Data, ++ service: web::Data>>, ++) -> Result { ++ log::debug!("attestation request is coming"); ++ let request = request.0; ++ let mut challenge = request.challenge; ++ if challenge == "" { ++ let cookie = http_req.cookie("oeas-session-id").ok_or(Error::CookieMissing)?; ++ let session = map ++ .session_map ++ .get_async(cookie.value()) ++ .await ++ .ok_or(Error::CookieNotFound)?; ++ if session.is_expired() { ++ return Err(Error::SessionExpired); ++ } ++ log::debug!("session challenge:{}", session.challenge); ++ challenge = session.challenge.clone(); ++ } ++ ++ let nonce = base64_url::decode(&challenge).expect("base64 decode nonce"); ++ let evidence = base64_url::decode(&request.evidence).expect("base64 decode evidence"); ++ let ids = request.policy_id; ++ let token = service.read().await.evaluate(&nonce, &evidence, &ids).await?; ++ ++ Ok(HttpResponse::Ok().body(token)) ++} ++ ++#[derive(Deserialize, Serialize, Debug)] ++pub struct ReferenceRequest { ++ refs: String ++} ++ ++#[post("/reference")] ++pub async fn reference( ++ request: web::Json, ++ service: web::Data>>, ++) -> Result { ++ let request = request.0; ++ log::debug!("reference request: {:?}", request); ++ match service.read().await.register_reference(&request.refs).await { ++ Ok(_) => Ok(HttpResponse::Ok().body("set reference success")), ++ Err(_err) => Ok(HttpResponse::Ok().body("set reference fail")), ++ } ++} ++ ++#[derive(Deserialize, Serialize, Debug)] ++pub struct PolicyRequest { ++ tee: String, ++ id: String, ++ policy: String, ++} ++ ++#[post("/policy")] ++pub async fn set_policy( ++ request: web::Json, ++ service: web::Data>>, ++) -> Result { ++ let request = request.0; ++ log::debug!("set policy request: {:?}", request); ++ let policy_id = request.id.clone(); ++ let policy = request.policy.clone(); ++ let dir:String = String::from(DEFAULT_POLICY_DIR); ++ match service.read().await.set_policy(&policy_id, &policy, &dir).await { ++ Ok(_) => Ok(HttpResponse::Ok().body("set policy success")), ++ Err(err) => { ++ log::debug!("set policy error: {:?}", err); ++ Ok(HttpResponse::Ok().body("set policy fail")) ++ } ++ } ++} ++ ++#[get("/policy")] ++pub async fn get_policy( ++ request: HttpRequest, ++ service: web::Data>>, ++) -> Result { ++ log::debug!("get policy request: {:?}", request); ++ let dir:String = String::from(DEFAULT_POLICY_DIR); ++ match service.read().await.get_policy(&dir).await { ++ Ok(ret) => Ok(HttpResponse::Ok().body(ret)), ++ Err(_err) => Ok(HttpResponse::Ok().body("get policy fail")), ++ } ++} +diff --git a/service/attestation/attestation-service/service/src/result/mod.rs b/service/attestation/attestation-service/service/src/result/mod.rs +new file mode 100644 +index 0000000..667e80f +--- /dev/null ++++ b/service/attestation/attestation-service/service/src/result/mod.rs +@@ -0,0 +1,55 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use thiserror::Error; ++use actix_web::{body::BoxBody, HttpResponse, ResponseError}; ++pub type Result = std::result::Result; ++ ++#[derive(Debug, Error)] ++//#[non_exhaustive] ++//#[allow(missing_docs)] ++pub enum Error { ++ #[error("IO error: {source:?}")] ++ Io { ++ #[from] ++ source: std::io::Error, ++ }, ++ ++ #[error("Web error: {source:?}")] ++ Web { ++ #[from] ++ source: actix_web::error::Error, ++ }, ++ ++ #[error("Deserialize error: {source:?}")] ++ Deserialize { ++ #[from] ++ source: serde_json::Error, ++ }, ++ ++ #[error("Request cookie is missing")] ++ CookieMissing, ++ ++ #[error("Request cookie is not found")] ++ CookieNotFound, ++ ++ #[error("The session of request cookie is expired")] ++ SessionExpired, ++ ++ #[error(transparent)] ++ Other(#[from] anyhow::Error), ++} ++ ++impl ResponseError for Error { ++ fn error_response(&self) -> HttpResponse { ++ HttpResponse::InternalServerError().body(BoxBody::new(format!("{self:#?}"))) ++ } ++} +diff --git a/service/attestation/attestation-service/service/src/session.rs b/service/attestation/attestation-service/service/src/session.rs +new file mode 100644 +index 0000000..5f191a7 +--- /dev/null ++++ b/service/attestation/attestation-service/service/src/session.rs +@@ -0,0 +1,58 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use actix_web::cookie::{time::{Duration, OffsetDateTime}, Cookie}; ++use scc::HashMap; ++use uuid::Uuid; ++ ++pub struct Session { ++ pub id: String, ++ pub challenge: String, ++ timeout: OffsetDateTime, ++} ++ ++impl Session { ++ pub fn new(challenge: String, timeout_m: i64) -> Self { ++ let id = Uuid::new_v4().as_simple().to_string(); ++ let timeout = OffsetDateTime::now_utc() + Duration::minutes(timeout_m); ++ Session { ++ id, ++ challenge, ++ timeout, ++ } ++ } ++ pub fn is_expired(&self) -> bool { ++ return self.timeout < OffsetDateTime::now_utc(); ++ } ++ pub fn cookie(&self) -> Cookie { ++ Cookie::build("oeas-session-id", self.id.clone()) ++ .expires(self.timeout.clone()) ++ .finish() ++ } ++} ++ ++pub struct SessionMap { ++ pub session_map: HashMap, ++} ++ ++impl SessionMap { ++ pub fn new() -> Self { ++ SessionMap { ++ session_map: HashMap::new(), ++ } ++ } ++ pub fn insert(&self, session: Session) { ++ let _ = self.session_map.insert(session.id.clone(), session); ++ } ++ pub fn delete(&self, session: Session) { ++ let _ = self.session_map.remove(&session.id); ++ } ++} +\ No newline at end of file +diff --git a/service/attestation/attestation-service/tests/Cargo.toml b/service/attestation/attestation-service/tests/Cargo.toml +new file mode 100644 +index 0000000..0fde476 +--- /dev/null ++++ b/service/attestation/attestation-service/tests/Cargo.toml +@@ -0,0 +1,9 @@ ++[package] ++name = "tests" ++version = "0.1.0" ++edition = "2021" ++ ++[dependencies] ++serde_json = "1.0.116" ++reqwest = {version = "0.12.5", features = ["blocking"]} ++rand = "0.8.5" +diff --git a/service/attestation/attestation-service/tests/src/lib.rs b/service/attestation/attestation-service/tests/src/lib.rs +new file mode 100644 +index 0000000..abd099f +--- /dev/null ++++ b/service/attestation/attestation-service/tests/src/lib.rs +@@ -0,0 +1,166 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++#[cfg(test)] ++mod tests { ++ use rand::{distributions::Alphanumeric, Rng}; ++ use reqwest::blocking::Client; ++ use serde_json::{json, Value}; ++ use std::thread; ++ ++ #[test] ++ fn api_register_reference_test() { ++ let request_body = json!({ ++ "refs":r#"{ "RIM": "7d2e49c8d29f18b748e658e7243ecf26bc292e5fee93f72af11ad9da9810142a", ++ "PRV": "cGFja2FnZSBhdHRlc3RhdGlvbgppbXBvcnQgcmVnby52MQpleHBlY3Rfa2V5cyA6" ++ }"# ++ }); ++ ++ let client = Client::new(); ++ let endpoint = "http://127.0.0.1:8080/reference"; ++ let res = client ++ .post(endpoint) ++ .header("Content-Type", "application/json") ++ .body(request_body.to_string()) ++ .send() ++ .unwrap(); ++ println!("{:?}", res); ++ assert!(res.text().unwrap().contains("success")); ++ } ++ ++ #[test] ++ fn api_register_concurrently() { ++ let mut thread_all = vec![]; ++ let thread_cnt = 100; ++ for _i in 0..thread_cnt { ++ thread_all.push(thread::spawn(|| { ++ let mut request_body = json!({ ++ "refs":r#"{ "RIM": "7d2e49c8d29f18b748e658e7243ecf26bc292e5fee93f72af11ad9da9810142a", ++ "PRV": "cGFja2FnZSBhdHRlc3RhdGlvbgppbXBvcnQgcmVnby52MQpleHBlY3Rfa2V5cyA6" ++ }"# ++ }); ++ let rng = rand::thread_rng(); ++ request_body["value"] = Value::String( ++ rng.clone() ++ .sample_iter(&Alphanumeric) ++ .take(64) ++ .map(char::from) ++ .collect(), ++ ); ++ ++ let client = Client::new(); ++ let endpoint = "http://127.0.0.1:8080/reference"; ++ let res = client ++ .post(endpoint) ++ .header("Content-Type", "application/json") ++ .body(request_body.to_string()) ++ .send() ++ .unwrap(); ++ println!("{:?}", res); ++ assert!(res.text().unwrap().contains("success")); ++ })); ++ } ++ for hd in thread_all { ++ match hd.join() { ++ Ok(_) => {} ++ Err(_) => { ++ assert!(false) ++ } ++ } ++ } ++ } ++ ++ #[test] ++ fn api_register_complex_reference_test() { ++ let request_body = json!({ ++ "refs":r#"{"complex_ref":{"level1_1":[1,2,3],"level1_2":{"name1":"value1"}}}"# ++ } ++ ); ++ ++ let client = Client::new(); ++ let endpoint = "http://127.0.0.1:8080/reference"; ++ let res = client ++ .post(endpoint) ++ .header("Content-Type", "application/json") ++ .body(request_body.to_string()) ++ .send() ++ .unwrap(); ++ println!("{:?}", res); ++ assert!(res.text().unwrap().contains("success")); ++ } ++ ++ #[test] ++ fn api_set_policy() { ++ let request_body = json!({ ++ "tee":"KUNPENG", ++ "id": "test_policy.rego", ++ "policy":"cGFja2FnZSBhdHRlc3RhdGlvbgppbXBvcnQgcmVnby52MQpleHBlY3Rfa2V5cyA6PSBbIlJJTSIsICJSUFYiXQppbnB1dF9rZXlzIDo9IG9iamVjdC5rZXlzKGlucHV0KQpvdXRwdXRbZXhpc3RdIDo9IGlucHV0W2V4aXN0XSBpZiB7CiAgICBzb21lIGV4aXN0IGluIGV4cGVjdF9rZXlzCiAgICBleGlzdCBpbiBpbnB1dF9rZXlzCn0Kb3V0cHV0W2V4aXN0XSA6PSBudWxsIGlmIHsKICAgIHNvbWUgZXhpc3QgaW4gZXhwZWN0X2tleXMKICAgIG5vdCBleGlzdCBpbiBpbnB1dF9rZXlzCn0Kb3V0cHV0WyJPdGhlciJdIDo9ICJvdGhlciIgaWYgewogICAgInRlc3QiIGluIGlucHV0X2tleXMKfQ" ++ } ++ ); ++ ++ let client = Client::new(); ++ let endpoint = "http://127.0.0.1:8080/policy"; ++ let res = client ++ .post(endpoint) ++ .header("Content-Type", "application/json") ++ .body(request_body.to_string()) ++ .send() ++ .unwrap(); ++ let response = res.text().unwrap(); ++ println!("set policy reponse: {}", response); ++ assert!(response.contains("success")); ++ } ++ ++ #[test] ++ fn api_get_policy() { ++ let client: Client = Client::new(); ++ let endpoint = "http://127.0.0.1:8080/policy"; ++ let res = client ++ .get(endpoint) ++ .send() ++ .unwrap(); ++ assert_eq!(res.status(), reqwest::StatusCode::OK); ++ println!("{:?}", res.text().unwrap()); ++ } ++ ++ #[test] ++ fn api_evaluate() { ++ let request_body = json!({ ++ "policy_id":["test.rego", "test_policy.rego"], ++ "challenge":"71oZilAy6vXCgFuRUhAYNA", ++ "evidence": "eyJ0ZWUiOiJJdHJ1c3RlZSIsImV2aWRlbmNlIjoie1xuXHRcInJlcG9ydF9zaWduXCI6XHR7XG5cdFx0XCJzY2Vfbm9fYXNcIjpcdFwiQjJEUE1NbWRUT0lVN3FpNnFCc3NaOEhFN1gtRnlwQVF3Ml9zWUpjNVVoS0FIZlFUM3phZTM5cnN6TEFzaE5qOGJ5dEIyOHNTUnp2N3RXYmRPSmV3dW5Uc1pUNnJaSEFRWFFEc1k0UzloOFRIdDBPNnlnbUV6Z1MydjZkM0NpeW91MGtQanNVbzFFbHpxbU5KS0JTbFFpejNqQlVzTjZhVXo3dkM5Yllpd3FsdWpBZm9iUlBfT19OM193NGxfMmQ4T05OaWtWRHdGcE5zMjJqcVJ0ZzlxS2VvWkduZVhUSGQwYVIzMVNKTDhsRFJsOG5Ka0FLdkRFUHZ5Zl9GN0Jrd2pEYk5YM2hNdmJMLXFEWkVWT2JNdUcwYldBVGRJV0FUTDFMem9qbDRTUVNPZDNmMEc1VWg1QU9pMEJtcDZUT3ZVTG44c0FpMWkwenF3U3h3Y0U0dlJjSG56OEVwTndTcjdET2tmUXR3bkdCY21fZUZkcTYzYXAtaWN6ZWwxa2pZUFRHZXY0bEdpemt4Wm9VN3FfYTExUXJIc1dkYnppeDBaNHlpMnBWS21lUXB0TjNydmxIYXZzZXE5VTh5VXBwbkVoMnNhVzJ3QlJmS2hYSVIxRFhiTlpNOV9qUHdRNVRTanNGQXpKYTNzbWM5VkxUMlZQa2lKSzBtNzhLS19sNkQ4TVF4ZXMyU2Q1dG9fYS1hcHh1OEE2b1E5aVZXRzBkdS0xS05MUm1hbVRCcUpLZzRfQzh0Z041dUZ3ejRLMVZ2eEYtVjY5RWVEUXpRV0o0SWFQTFNCS3BzSkx1ZUZyQjk1TGNmWnhudk05OG5oQVo4QU5PQ3pFdXJSYlVlR1MwcDM2ZjUtU3BYSGlveTNSbm5rY05tYmlVb2cwbVd6T01HVTE4WTZjeFZJVGNcIlxuXHR9LFxuXHRcImFrY2VydFwiOlx0e1xuXHRcdFwic2NlX25vX2FzXCI6XHR7XG5cdFx0XHRcInNpZ25hdHVyZVwiOlx0e1xuXHRcdFx0XHRcImRya19jZXJ0XCI6XHRcIlRVbEpSV3RxUTBOQk0zRm5RWGRKUWtGblNWSkZVbU40Tnk5NE5sRkRSRnBCUmpGS2IxRkJhMkZGVFhkRVVWbEtTMjlhU1doMlkwNUJVVVZNUWxGQmQxQlVSVXhOUVd0SFFURlZSVUpvVFVOUk1EUjRSSHBCVGtKblRsWkNRVzlVUW10b01WbFlaR3hoVkVWa1RVSnpSMEV4VlVWQmVFMVZVMGhXYUdReVZuQkpSV3hWU1VaQ2VXSXlVakZaTTFGblVUQkZkMGhvWTA1TmFrMTNUbFJGTVUxVVRYbFBSRVY2VjJoalRrMTZaM2RPVkVWNFRWUk5lVTlFUlhwWGFrRTJUVkZ6ZDBOUldVUldVVkZIUlhkS1JGUnFSVkJOUVRCSFFURlZSVU5vVFVkVFNGWm9aREpXY0UxU2IzZEhRVmxFVmxGUlJFVjRSWGROYWxwSlZXdFplRTFGY3pWTlJFRTBUMVJGZUZaRVEwTkJhVWwzUkZGWlNrdHZXa2xvZG1OT1FWRkZRa0pSUVVSblowbFFRVVJEUTBGbmIwTm5aMGxDUVUxek9FTllRMXBvZGtObk1qbE1UbXRWWWxOTU9VbGljR3RaVkhSM1IyWTBLMGhYYjJoRUt6QnNPRVl3YVRGR1dIbEVibnBJYW1keU1UTnNWakp1YkZCR09XeHVVazlNWTJKSlRDdHRVVFJJYm05UVEyWXZZVXhSZFdsRFZUTkVjRXhNVG5aMFIyZFhlbXBoTW5CbFRIVlhPVWxWWkhoMGNuUlNRalV6YVdKSFRHeHdSVkl2ZEZkaVNUSk5SMmhTTldaWFptUXdSVGR4ZFM5Q1VGWnRPVlpKVDB4eFEyMVdaMWhWY0hSblVucDNPSFpIUkZSc09GSjFPRkJ4WWt0WmRucGhUbHBxYlhwek5XNUtPWGx5VW5KSksydFBaekI2TTBwWU1tTllRVzVEVVVneGVDOWlWRzlXV0dodFkybFpPVU5MTTNCR04ydDRWSGxsZUdoR1RqVXhZakp6VVdWNVEzTlhhazVsYnpsWlVGSTBTMUJ0VVVzNVJYWm5kbkpXYW5keVRuZFNMMkZwZDFKWlowbzJTelExUzNwS1FUSnNjWFZ0VmtReFUzcFlORmhtWm14RFNqRlphRkpTWTNWeWFFc3ZaRXA0UVZkUlJsa3JTV2Q0Y1V4SE9IRlFWbGM0U1RFMFpuZzJRbWxUYkZWelIzRlVRWFp5TjFWRlF6aFNWWEJWT0VsT2VUSkpaV3A1TlhOaWMyTnBSVlpsTkVkSGFqUk5PVTB2YzBaVFQzUmlOM1FyVm5GU1RsbFRTV05HYlhKVVZqVlNjbHBDVkZkU1kyeGhOM3BXVkc1NFUwNHdjR2x5UzBWWmNFOVBVV1p6UWxodU56VjNNMFpsU0RKcU5HdEpVV3RWYUVSNU9XcFhaa05sSzFONGVuQnZTMkpvVmxrd1dsbHNVRzl1U2s5QlowaEhWek5UU2tac2JpdG1Ra3BCZFdkc1RqbFphbVJ2UVZkcVZHcDRSRTB6ZFZWdGVUQjFkRFkxU20xME5uQkNkVU1yVVVwWFIySjFhQ3RFTUV0MFNVVnROVEZMVFM5RE1HUTRNME0yYm1GSk1qVmxla0o2T0ZGalpXbG5kRXg0U1UxYVprSkZWU3RYTm1aeU9HaHhWa1pDTldkRFIzVldiRzVUUTJSNmJFbDBlRXc0VldOeWVISmpRazVQWkRSMWNIZGxiMWt4ZEVOS1VUSTVXRGNyV0RjemRVVnNkVVpJUVdkTlFrRkJSMnBuV1RoM1oxbDNkMGgzV1VSV1VqQnFRa0puZDBadlFWVkZiMjh6TjFCc2VsWTVabXRuTjNFeFluZFFjek5oTkZKVWN6aDNRM2RaUkZaU01GQkNRVkZFUVdkUU5FMUdkMGREUTNOSFFWRlZSa0ozUlVKQ1JrRjNWR3BCYjBKblozSkNaMFZHUWxGamQwRnZXV05oU0ZJd1kwUnZka3g2UlhsT2VUUjNUR3BCZFUxVE9XcFpWMng2WXpOV2JFeHRhREJpVkVGcFFtZG5ja0puUlVaQ1VXTjNRVmxaVjJGSVVqQmpSRzkyVEhwRmVVNTVOSGRNYWtGMVRWUnZlVTFFVVRCTmVrRk9RbWRyY1docmFVYzVkekJDUVZGelJrRkJUME5CVVVWQlQzWlVXR1ZFWlZSVE1VNTVibEEzY0ZZMFYwdzBZMFp1UVdoaFNFbE5hbWhwYlZjM1RqbEZRblF3VTB4SFNGQXpaRGhzTTBsblYxSkZiMlpRTkZnd1JWSjFiR1J4WWxweFpucElSWEZhZG5abFZFOVNVVlZHYVdWV1ZURnJLekY0U1doRFUzbGtNMWhxVUdObGQxWkRiMFpDY0dNMlFURlFlbkJDY1dwM1kyTXpLMDFxTUdwcVNXaFpWMDAxVnl0b1VYRlJOVWhuT1RneWFWUmhNM2haTjBGc1UzaExVa2x2YkN0dU16SkpRM2hXTkUxbFQwdzNUa0ZoWVhkVlpGRkdOamh3ZHpGQk5tSmtXV2d5VjNWSWVHWXJaQ3RUWm5aVEwzZGxjVEJFV21rNGRUSnNURzFoVVZjd2JrcEVTVFUzZFcxWFEyTTNTa2d2VkZaR0wzSmlhaXRrZVhCUlJUSkJaa3h2VjNwS2JuUkRaa3d5UjI1eVlrZ3JXbkpPVHpSQ1l6RXpOWEZaU2xad1ZqTnNVSFpOYzFVeVozRk9jRFpvVmtndkt6UkJhRTQyWjFFNVYwbzFkbUprV25aT2VsZGpURlpHYzB4UlBUMFwiLFxuXHRcdFx0XHRcImRya19zaWduXCI6XHRcIkVTLUhLSUFyOTBYQ1h3ZXRfQi1pR1BmS0Uwa0VIdWczeGhnSUpRUWNRak5iSXc5bHJ4bFZaZE1XMnlIM3hfVlNMTUhfZ3RzWjVDSXRjYlpjNlVNaWhCeDRDcXVpdjZ5RG1yMlVOUmJrNGdKd2ZiNjE5em9pNkEzaThTcFpaaUctY2dQUlNGS05IdjNSV0tBbDdXaUd1SU04ZFlvdHR0eHhJT3U0bElrQWlXR2txRlpCUzJCc2JSNXVLdEl2R0hEaG1QZEh5c3JwU3lrb0loVE4wM0FpY0NNYWRFVnhzbUs5Y0pGRlFTOHg3ZlAxU0VMUHJDMFdUT0F4bjlNdFBRVXcwSnV5dzZKeXVJZU5BYjRYRV9uNjZQOU9oRXV5Y3RjajBsa3RxLUpneFFlalRVejFROHVTbVFEV2ROcUZlRTFOQWk3Zmd6YVNjd2tTc3lXa0dtckt0MFZaSnI2a0hCT0xYMFNEMElPRWJROFNRdHFieVRpRko4Uk4tMXZqb05LYjhobnpIUnpqeGJ4eGYtVW9zeWlhY3RNUVRnOUNlTi1rSVRjUDJTUVE4ZjA0OThVVkpRamFoU05OeVcxdnp6c0hXaFQyeWMtcFdwSll6YUtrXzBjdFBVT3UwazJ1VS1EbENOdFQzRkVpS0hMZWVzRmdUSUlmdFJ1Z1JoMklHSFVkcFlvTmJDVmxvRXdaLWRPY19fQ212ZTBOOVVoWWdSUUpER1VhOGRWZVhKVXZXZ2dNQXpJdEE0QUhhVlByTFRFSTRHQld5bEw5Wm1FclU5bmpXZjd3X09XcldvaUFlVm5KMl9yanhpYjVUZ3ZldXJCM3pCbEJMSTVwQ0ExbmxhT0N0aUlMM0c1dEs3N3NNXzNXeUhYSG96cFRTeTR3WG5mU0tGeHUweXROV240XCJcblx0XHRcdH0sXG5cdFx0XHRcInBheWxvYWRcIjpcdHtcblx0XHRcdFx0XCJ2ZXJzaW9uXCI6XHRcIlRFRS5SQS4xLjBcIixcblx0XHRcdFx0XCJ0aW1lc3RhbXBcIjpcdFwiOTU1ODA5NDE0MTIzNTRcIixcblx0XHRcdFx0XCJzY2VuYXJpb1wiOlx0XCJzY2Vfbm9fYXNcIixcblx0XHRcdFx0XCJzaWduX2FsZ1wiOlx0XCJQUzI1NlwiLFxuXHRcdFx0XHRcImhhc2hfYWxnXCI6XHRcIkhTMjU2XCIsXG5cdFx0XHRcdFwicXRhX2ltZ1wiOlx0XCI3V2N1ZjZLMEM0XzBUTVIwZEMyMURBdVZ2c0c2WEIySjhxZ0dtUUFHSVdzXCIsXG5cdFx0XHRcdFwicXRhX21lbVwiOlx0XCJQVXJNdENoSGRzaW1VQWZrRnZrc3Y1Z3ZHMzFSalgtbF8wb0JsLUpCZHNNXCIsXG5cdFx0XHRcdFwidGNiXCI6XHRcIlwiLFxuXHRcdFx0XHRcImFrX3B1YlwiOlx0e1xuXHRcdFx0XHRcdFwia3R5XCI6XHRcIlJTQVwiLFxuXHRcdFx0XHRcdFwiblwiOlx0XCJwUkU1OGtzdW9IeGNMMExiMWs1blNfZkt2eERZSm96anhnYlh0aVU5T0RJNFMxa1hSZ2ZBNWdiRi1BQVJkSDZZOTdzQ0t6TklBNFRZd0pVTWh0ZFlScTZZYWZrSFZLb29tUWhJYU5mSUFaMG1BWXU5V2ttS2hBNE9BRjJNX0xDMW03WVVGMlI1S2taaGxzeFUtMEo3bWwyM2Q3RFB1enI1cnJwREFDYWxqMmZtLTROVk5DMHdYTjdPaWVXSW9UdFF0Sk9DSW5aODFjZ3FWZUxhUEVUSnpmRnBtX0ZBWXgtWDlsVnlrUFVxeWNuZnVFZjJ1Tml2OVNyMHc1d1NlX1BSRmlNQmRCRGxSQllqODBsbHllbGZGb0tWeE41c1BXMENuRkJZdlA2MHV6VVNSMDZjbHZxSU4wWUotTnhEcVhMU1VrajAtZEluNHdFLTVaaU55azI1Q25mWE5tMDk1c2tOeFo4SUxYN3FIZEJkVFhKczYxQ3J1TllsUWg0S2pHZVpWZkxVWkJZaVIwZC1JbjMyWDBYakQ1QWw4OHhLNFdxWFNhdFF6N01MekhBLURKYU8ybHNKZEFxTGFqdGxnV2ZOYXBWbWd5cVB1OFFVTkhfaEhqQlhTUDRNMXEwTUFTd2JCWXlib1VVMl9CY0lSZ0pwTnNneHlRNTJibTU2aUppYVFRQ05rYU55R1FhajRxWEVodjFTQlFlYjlMSVR1dVZfRDQzLUdhS2Z3dEhWZEZiYXdvYjZFbm5yWHoxVW1fREt1dXhLU09QZ3E0MEpRanFCQllMa1J2cXJfbS1ES3BManlVb2w4MS1ldUU1bjNZejFXU1lIbG5tc1hfMEYzbFZDQktQelR5aWd5SGZvZ0hjVXNtX2tTVDFfbWdIajg2ZnZ2OVJGTFFaTFBJRVwiLFxuXHRcdFx0XHRcdFwiZVwiOlx0XCJBUUFCXCJcblx0XHRcdFx0fVxuXHRcdFx0fSxcblx0XHRcdFwiaGFuZGxlclwiOlx0XCJwcm92aXNpb25pbmctb3V0cHV0XCJcblx0XHR9XG5cdH0sXG5cdFwicGF5bG9hZFwiOlx0e1xuXHRcdFwidmVyc2lvblwiOlx0XCJURUUuUkEuMS4wXCIsXG5cdFx0XCJ0aW1lc3RhbXBcIjpcdFwiOTU1ODExNzg4NzA1MzRcIixcblx0XHRcIm5vbmNlXCI6XHRcIjcxb1ppbEF5NnZYQ2dGdVJVaEFZTkFcIixcblx0XHRcImtleVwiOlx0e1xuXHRcdFx0XCJwdWJfa2V5XCI6XHR7XG5cdFx0XHRcdFwiblwiOlx0XCJ1LUtoY1RGLTRMdVhzTUtJZ0I0a0hDMG9NbVZXOV9OUGFGRlB0ajRfSG15azd0b0psS09BMVFENFRiY0dGYUdzenk3aUhvUm9hSUphMnJ6djFuUm1tZ3JlN0lRbTZaSXN2Z3M2bm80OW4ybUJtcENOVDlacEtXemJEMjN3N2hya3VUdkY5LUlTalhMRDk4Tk1ycF9OdUxXalNCczJPV3hyWGJrdUdjU1pETXN5NjJ6dkYxclhIYXFRRmFKNXhpU3cyaGU3YS01YWFfUGpuQmtMa0VYOE1ySF9RSzBtUFI1cmFNeU85a25CVU9UT0owdUpVS3hIQ2dKSEpTU051aDY1ZUt4VlJCS3pKbzNrT0d3Z1lRdjltOEJsZUhjeWoyWlZBVUhfZFFrajRZZFQ3bjAyZ3VBam9RMVR4aHl6R250RnV1VTFTVUZDRVAwZzN3dmI5c2N4UVNmZlNacjl5WTlCMElBR1REckZ3Q0dPM3JUamF3NXJxY2FxSlprY0pkMjFIU05DNTJUOEdENHo4UWhfV05iUHNueV9sRXI5VzU5ZHBXN0FjWlJvSkZlMHhiOGhSaURwRldmOTZJdnBTT2lQZ2RNQVY5SHRjeFRoOS1pdzJiYl9kQzYtekVLS2xfTUtzWE1rSk1DdmxDazdCd3dQX1ZKVnQ2OS1pLVo0dWN3YXhOQUxCdHJfZWFQU0RPNHU2d2JDNUl1bGlPb2VvM0hEci1FQ3RwWVdWWVplVXBGTUIwZF9GU3hsT3NXQjE3QzRNT3ZzNjVtQWcwYXJNOTMyb00xeE5WUWxfYzZkS1g4alVzV1FubXpDcG5zS1JCU3UwRHFwU0JORjhNSnY4ZDR2cFRoNFU2NmhHWFhRYjVWZjdDWkpCbC05dFZRMGJHQTVhNjFfbkYxRXhtc1wiLFxuXHRcdFx0XHRcImVcIjpcdFwiQVFBQlwiLFxuXHRcdFx0XHRcImt0eVwiOlx0XCJSU0FcIlxuXHRcdFx0fSxcblx0XHRcdFwiZW5jX2tleVwiOlx0XCJWa3hVX1FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBakJBQUFBQUFBQUNBQUFBQUFBQUFBREFBQUFBQUFBQUFNQUFBQUFBQUFBQkFBQUFBQUFBQUFFQUFBQUFBQUFBRHQ1TnJxQUpocjE1N0Q5V0QyTDRCdE9nVnlXejJfQzZTLXNLc2lzb0xpakRfWmtGM29GenpnRHV1MG04XzNSRk9oc2xTV1A4MTNJTVB3SUZMRDN4MkhSOG9VbkUyYS1fT2UwY1diRGZrQUtHLUxKaS0zNzFoNWs4T25lMHV0UmhyV1lTRnBCUS1lWGlkdlJEcUg3NUFpcDlNemJ5TExYTUlvbm1LWXlXa3EzczJMWVRiX1g1UkhMSnZqVjd0cDZxS20ySnl3TzY3TUdiVGdTUzJsbzc1SGJIaVQ5dmFBZldaUFF5Y1FDMHROTlhfRUJfUUYyTWJBckpibi1UM3NaTVhya195X3RDempMYTNWZWJXS2lsUGV5aXpYWFFBb1cyc0Q5MGZybEZYazhwMU9lTGhxcWxLVml3di1rUlk0ZU5ISnJXOGVTdTZmQndLUGw4dWJUcGt3eV9ickprYmFXUVVoYVk5ZFN5Wml5bWhPZHFTTGw4dTRwRnZvd0xKdDJQanpWajhDRTNQM0M4OEtSMkdsNjRvaUF3dnk2OWVsT1E3aXRXa1VjLTZlUDR2X0paT1VOcVZUN2hCUzNKWEJ4MmxjeS0yc0lEc1FhMGhSSW10eERFZXhoendWUFJrM2xwQkFwSlljcTR6LUVHWGV6dm5Kb0NERVRHeHVMSHl3MkctRV9lY1FnTTlyVTVQOW9GTG5fSm96OFBBYkJ6ZzgwOURwc0lKM051Slp3ZkhoNkpyNHlRRXZvTHphTFhsa1VVS0IxQXhjWXphLV9tT0R6WEJnRXRHRnBpWk9UYkFoRUhSWXRzQ25paUpDbTR6Mk9nay1HY1YwdVl1cTlCZDhreGIyVnU2NTdqVG1XVGxPVVE5SnZ1bXZ4Mks3MnJyakZBdzRpVy1PSUFab2JpdmVoN1RtUjgwaFdQaThOcTlBd1BGYm9zVWZwWDUzeUNZWUNpSk15ZzUwRUpQZXBwaFduS2NReERGNUNZMjBXSkpEXzN5REJ6WEFOeDdJSmE3NUVGMGRRVm9BSXEtVW1nODJHZWMzdWdrUEtTTXl0RHZNMmZUVlRPMl9QYlJETkFlbFpoZ3VEX0hZMDhhRTVybVE0NTFPLVFlR3pnbWw3QWpNaHJBTVZwQlp1VEpneFBNRGpWaTFWZHZ2ZzlaLWVobTlwYzFENzRCQ1hwdEZFUEVFRHhjd1dvaEhkLXExVTMtczZQTVlRbUs4QzJEQ25zekUzZ0xueTZJQW9PZ19zSGJUT1Jha1dYWnlhZkNMelJ4NmxCaFlyNkZ4OHo1Y1BIOXQ1VEstcmVqVjdPTURZbWVaUFl5QmFCbE5NYzNRbVo4Tk94d0Z2bWN1MnhEMVU4cjZqSXRqR3BVdUN5RDEzRGZnZ0REVmZWem9HSldXVHROMXVLWkNfRHVQdi12blRXdFIwZjQ4cTd6dDY1WXVpVC04NFptUXQ0Q1NMTlZNb2plR2lDbXN6bVZ1Y1ExSENxcklsQVBtM243UTJDSmx6ZEdOUWZyV3FJNmU3VHo2VGg2UWVBUkZCYzM5VVI4RjlMaHpDc2dYcGI1NWRTb0E5UXVDTEswdzRWNnFjWUE5QmJkZzRlb1ZRV1J1V1ljTDlKMl92TGZON3ROSWpCa01rTmhmZDZleXk4M2JrUmpuY3p5UFh6UllKUXF2RkppN3Y3ZjhtS0lSV1VkemNPSzhDOFB6UkJvdFR6SXZqdm9XWkpfcW45OHhWVlRJTU1pYWhZRF9ldHE5cjVTeWpTR2ZHM25nVzllblJ6a2FrVDdIc1V0MjYyalpWekxITzJKUGN0X0h2T1RkVFNWeW9wZjFUYUZlY1dCSHdaMVA2NUZpcXFueFhZam9XVnZyWlc1dmg1UGgtUzBKZ2V5U0Z1NndBWldwTkhEbWtNOGhvR09PVzMzRDZUaTFYeXY1VGpvYTI3ZmlZdGdZd3RWUTBHb2RodTBYN3NxN0Y4UnVjWW55M05yYUtJSXpRaUp4ZHRlSGNzQnpxUTRDWk5JSlNuMFZzellVQkRxR0t1Y0RmeVhVXCJcblx0XHR9LFxuXHRcdFwic2NlbmFyaW9cIjpcdFwic2NlX25vX2FzXCIsXG5cdFx0XCJ1dWlkXCI6XHRcImY2OGZkNzA0LTZlYjEtNGQxNC1iMjE4LTcyMjg1MGViM2VmMFwiLFxuXHRcdFwicmVxdWVzdGVyX3V1aWRcIjpcdFwiXCIsXG5cdFx0XCJoYXNoX2FsZ1wiOlx0XCJIUzI1NlwiLFxuXHRcdFwic2lnbl9hbGdcIjpcdFwiUFMyNTZcIixcblx0XHRcInRhX21lbVwiOlx0XCJRYjkyZWUxTlRnZlVpdnlISXZuaEVZcU5IU0tLS0FPVUlzV1Y5dVl0eXA4XCIsXG5cdFx0XCJ0YV9pbWdcIjpcdFwiQW1VLXI4OHREOVhVSlMwOGhkVkJTREFySDE0WnI3UXFRQlpwLXp2WkE5a1wiLFxuXHRcdFwidGFfYXR0clwiOlx0XCJcIixcblx0XHRcInRjYlwiOlx0XCJcIlxuXHR9LFxuXHRcImhhbmRsZXJcIjpcdFwicmVwb3J0LW91dHB1dFwiXG59In0", ++ } ++ ); ++ let client = Client::new(); ++ let endpoint = "http://127.0.0.1:8080/attestation"; ++ let res = client ++ .post(endpoint) ++ .header("Content-Type", "application/json") ++ .body(request_body.to_string()) ++ .send() ++ .unwrap(); ++ assert_eq!(res.status(), reqwest::StatusCode::OK); ++ println!("{:?}", res.text().unwrap()); ++ } ++ ++ #[test] ++ fn api_get_challenge() { ++ let client: Client = Client::new(); ++ let endpoint = "http://127.0.0.1:8080/challenge"; ++ let res = client ++ .get(endpoint) ++ .send() ++ .unwrap(); ++ assert_eq!(res.status(), reqwest::StatusCode::OK); ++ println!("{:?}", res.text().unwrap()); ++ } ++ ++} +diff --git a/service/attestation/attestation-service/token/Cargo.toml b/service/attestation/attestation-service/token/Cargo.toml +new file mode 100644 +index 0000000..c4b885c +--- /dev/null ++++ b/service/attestation/attestation-service/token/Cargo.toml +@@ -0,0 +1,13 @@ ++[package] ++name = "token_signer" ++version = "0.1.0" ++edition = "2021" ++ ++# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html ++ ++[dependencies] ++jsonwebtoken.workspace = true ++serde.workspace = true ++serde_json.workspace = true ++anyhow.workspace = true ++attestation-types.workspace = true +\ No newline at end of file +diff --git a/service/attestation/attestation-service/token/src/lib.rs b/service/attestation/attestation-service/token/src/lib.rs +new file mode 100644 +index 0000000..ed41a4e +--- /dev/null ++++ b/service/attestation/attestation-service/token/src/lib.rs +@@ -0,0 +1,115 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use anyhow::{Result, bail}; ++use jsonwebtoken::{encode, get_current_timestamp, ++ Algorithm, EncodingKey, Header, ++}; ++use std::path::Path; ++use serde::{Deserialize, Serialize}; ++use serde_json::Value; ++use attestation_types::{EvlResult, Claims}; ++ ++ ++#[derive(Debug, Clone, Serialize, Deserialize)] ++pub struct TokenSignConfig { ++ pub iss: String, ++ pub nbf: usize, // 生效时刻 ++ pub valid_duration: usize, // 有效时间 ++ pub alg: SignAlg, ++ pub key: String, ++} ++ ++impl Default for TokenSignConfig { ++ fn default() -> Self { ++ TokenSignConfig { ++ iss: "oeas".to_string(), ++ nbf: 0, ++ valid_duration: 300, ++ alg: SignAlg::PS256, ++ key: "/etc/attestation/attestation-service/token/private.pem".to_string(), ++ } ++ } ++} ++ ++ ++#[derive(Debug, Clone, Serialize, Deserialize)] ++pub struct EvlReport { ++ pub tee: String, ++ pub result: EvlResult, ++ pub tcb_status: Value, ++} ++ ++pub type SignAlg = Algorithm; ++pub struct TokenSigner { ++ pub config: TokenSignConfig, ++} ++ ++impl Default for TokenSigner { ++ fn default() -> Self { ++ TokenSigner { ++ config: TokenSignConfig::default(), ++ } ++ } ++} ++ ++impl TokenSigner { ++ pub fn new(config: TokenSignConfig) -> Result { ++ Ok(TokenSigner { config }) ++ } ++ fn support_rs(alg: &Algorithm) -> bool ++ { ++ if *alg == Algorithm::RS256 || *alg == Algorithm::RS384 || *alg == Algorithm::RS512{ ++ return true; ++ } ++ return false; ++ } ++ fn support_ps(alg: &Algorithm) -> bool ++ { ++ if *alg == Algorithm::PS256 || *alg == Algorithm::PS384 || *alg == Algorithm::PS512 { ++ return true; ++ } ++ return false; ++ } ++ pub fn sign(&self, report: &EvlReport) -> Result { ++ let alg: Algorithm = self.config.alg; ++ let mut header = Header::new(alg); ++ header.typ = Some("JWT".to_string()); ++ let unix_time = get_current_timestamp(); ++ let claims: Claims = Claims { ++ iss: self.config.iss.clone(), ++ iat: usize::try_from(unix_time).expect("unix time to usize error"), ++ nbf: usize::try_from(unix_time).expect("unix time to usize error"), ++ exp: usize::try_from(unix_time).expect("unix time to usize error") ++ + self.config.valid_duration, ++ evaluation_reports: report.result.clone(), ++ tee: report.tee.clone(), ++ tcb_status: report.tcb_status.clone(), ++ }; ++ if !Self::support_rs(&alg) && !Self::support_ps(&alg) { ++ bail!("unknown algrithm {:?}", alg); ++ } ++ if !Path::new(&self.config.key).exists() { ++ bail!("token verfify failed, {:?} cert not exist", self.config.key); ++ } ++ let key = std::fs::read(&self.config.key).unwrap(); ++ let key_value: EncodingKey = match EncodingKey::from_rsa_pem(&key) { ++ Ok(val) => val, ++ _ => bail!("get key from input error"), ++ }; ++ ++ let token = match encode(&header, &claims, &key_value) { ++ Ok(val) => val, ++ Err(e) => bail!("sign jwt token error {:?}", e), ++ }; ++ Ok(token) ++ } ++} +\ No newline at end of file +diff --git a/service/attestation/attestation-service/verifier/Cargo.toml b/service/attestation/attestation-service/verifier/Cargo.toml +new file mode 100644 +index 0000000..e870fa7 +--- /dev/null ++++ b/service/attestation/attestation-service/verifier/Cargo.toml +@@ -0,0 +1,27 @@ ++[package] ++name = "verifier" ++version = "0.1.0" ++edition = "2021" ++ ++[dependencies] ++anyhow.workspace = true ++serde.workspace = true ++serde_json.workspace = true ++async-trait.workspace = true ++cose-rust.workspace = true ++ciborium.workspace = true ++hex.workspace = true ++openssl.workspace = true ++log.workspace = true ++ima-measurements.workspace = true ++rand.workspace = true ++fallible-iterator.workspace = true ++attestation-types.workspace = true ++ ++[dev-dependencies] ++ ++[features] ++default = [ "itrustee-verifier","virtcca-verifier" ] ++itrustee-verifier = [] ++virtcca-verifier = [] ++no_as = [] +diff --git a/service/attestation/attestation-service/verifier/src/itrustee/itrustee.rs b/service/attestation/attestation-service/verifier/src/itrustee/itrustee.rs +new file mode 100644 +index 0000000..9749871 +--- /dev/null ++++ b/service/attestation/attestation-service/verifier/src/itrustee/itrustee.rs +@@ -0,0 +1,53 @@ ++/* automatically generated by rust-bindgen 0.69.4 */ ++ ++#[repr(C)] ++#[derive(Debug, Copy, Clone)] ++pub struct buffer_data { ++ pub size: ::std::os::raw::c_uint, ++ pub buf: *mut ::std::os::raw::c_uchar, ++} ++#[test] ++fn bindgen_test_layout_buffer_data() { ++ const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); ++ let ptr = UNINIT.as_ptr(); ++ assert_eq!( ++ ::std::mem::size_of::(), ++ 16usize, ++ concat!("Size of: ", stringify!(buffer_data)) ++ ); ++ assert_eq!( ++ ::std::mem::align_of::(), ++ 8usize, ++ concat!("Alignment of ", stringify!(buffer_data)) ++ ); ++ assert_eq!( ++ unsafe { ::std::ptr::addr_of!((*ptr).size) as usize - ptr as usize }, ++ 0usize, ++ concat!( ++ "Offset of field: ", ++ stringify!(buffer_data), ++ "::", ++ stringify!(size) ++ ) ++ ); ++ assert_eq!( ++ unsafe { ::std::ptr::addr_of!((*ptr).buf) as usize - ptr as usize }, ++ 8usize, ++ concat!( ++ "Offset of field: ", ++ stringify!(buffer_data), ++ "::", ++ stringify!(buf) ++ ) ++ ); ++} ++ ++#[link(name = "teeverifier")] ++extern "C" { ++ pub fn tee_verify_report( ++ data_buf: *mut buffer_data, ++ nonce: *mut buffer_data, ++ type_: ::std::os::raw::c_int, ++ filename: *mut ::std::os::raw::c_char, ++ ) -> ::std::os::raw::c_int; ++} +diff --git a/service/attestation/attestation-service/verifier/src/itrustee/mod.rs b/service/attestation/attestation-service/verifier/src/itrustee/mod.rs +new file mode 100644 +index 0000000..67c857a +--- /dev/null ++++ b/service/attestation/attestation-service/verifier/src/itrustee/mod.rs +@@ -0,0 +1,76 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++//! itrustee verifier plugin ++ ++use super::*; ++use log; ++use serde_json::json; ++use std::path::Path; ++use std::ops::Add; ++ ++mod itrustee; ++ ++const ITRUSTEE_REF_VALUE_FILE: &str = "/etc/attestation/attestation-service/verifier/itrustee/basevalue.txt"; ++ ++#[derive(Debug, Default)] ++pub struct ItrusteeVerifier {} ++ ++impl ItrusteeVerifier { ++ pub async fn evaluate(&self, user_data: &[u8], evidence: &[u8]) -> Result { ++ return evalute_wrapper(user_data, evidence); ++ } ++} ++ ++fn evalute_wrapper(user_data: &[u8], evidence: &[u8]) -> Result { ++ let mut in_data = user_data.to_vec(); ++ let mut in_evidence = evidence.to_vec(); ++ let mut data_buf: itrustee::buffer_data = itrustee::buffer_data { ++ size: in_evidence.len() as ::std::os::raw::c_uint, ++ buf: in_evidence.as_mut_ptr() as *mut ::std::os::raw::c_uchar, ++ }; ++ let mut nonce = itrustee::buffer_data { ++ size: in_data.len() as ::std::os::raw::c_uint, ++ buf: in_data.as_mut_ptr() as *mut ::std::os::raw::c_uchar, ++ }; ++ log::info!("input nonce:{:?}", nonce); ++ let policy: std::os::raw::c_int = 1; ++ if !Path::new(ITRUSTEE_REF_VALUE_FILE).exists() { ++ log::error!("itrustee verify report {} not exists", ITRUSTEE_REF_VALUE_FILE); ++ bail!("itrustee verify report {} not exists", ITRUSTEE_REF_VALUE_FILE); ++ } ++ let ref_file = String::from(ITRUSTEE_REF_VALUE_FILE); ++ let mut file = ref_file.add("\0"); ++ let basevalue = file.as_mut_ptr() as *mut ::std::os::raw::c_char; ++ unsafe { ++ let ret = itrustee::tee_verify_report(&mut data_buf, &mut nonce, policy, basevalue); ++ if ret != 0 { ++ log::error!("itrustee verify report failed ret:{}", ret); ++ bail!("itrustee verify report failed ret:{}", ret); ++ } ++ } ++ let js_evidence: serde_json::Value = serde_json::from_slice(evidence)?; ++ let payload = json!({ ++ "itrustee.nonce": js_evidence["payload"]["nonce"].clone(), ++ "itrustee.hash_alg": js_evidence["payload"]["hash_alg"].clone(), ++ "itrustee.key": js_evidence["payload"]["key"].clone(), ++ "itrustee.ta_img": js_evidence["payload"]["ta_img"].clone(), ++ "itrustee.ta_mem": js_evidence["payload"]["ta_mem"].clone(), ++ "itrustee.uuid": js_evidence["payload"]["uuid"].clone(), ++ "itrustee.version": js_evidence["payload"]["version"].clone(), ++ }); ++ let claim = json!({ ++ "tee": "itrustee", ++ "payload" : payload, ++ }); ++ Ok(claim as TeeClaim) ++} +diff --git a/service/attestation/attestation-service/verifier/src/lib.rs b/service/attestation/attestation-service/verifier/src/lib.rs +new file mode 100644 +index 0000000..58df3bd +--- /dev/null ++++ b/service/attestation/attestation-service/verifier/src/lib.rs +@@ -0,0 +1,80 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++//! Unified tee verifier ++//! ++//! This crate provides unified APIs to verify TEE evidence. ++ ++use anyhow::*; ++use serde_json; ++use async_trait::async_trait; ++ ++use attestation_types::{Evidence, TeeType}; ++ ++#[cfg(feature = "itrustee-verifier")] ++pub mod itrustee; ++ ++#[cfg(feature = "virtcca-verifier")] ++pub mod virtcca; ++ ++pub type TeeClaim = serde_json::Value; ++ ++#[derive(Debug, Default)] ++pub struct Verifier {} ++ ++#[async_trait] ++pub trait VerifierAPIs { ++ async fn verify_evidence(&self, user_data: &[u8], evidence: &[u8]) -> Result; ++ async fn verify_ima(&self, ++ evidence: &[u8], ++ claim: &serde_json::Value, ++ ) -> Result<()>; ++} ++ ++const MAX_CHALLENGE_LEN: usize = 64; ++ ++#[async_trait] ++impl VerifierAPIs for Verifier { ++ async fn verify_evidence(&self, user_data: &[u8], evidence: &[u8]) -> Result { ++ let len = user_data.len(); ++ if len <= 0 || len > MAX_CHALLENGE_LEN { ++ log::error!("challenge len is error, expecting 0 < len <= {}, got {}", MAX_CHALLENGE_LEN, len); ++ bail!("challenge len is error, expecting 0 < len <= {}, got {}", MAX_CHALLENGE_LEN, len); ++ } ++ let aa_evidence: Evidence = serde_json::from_slice(evidence)?; ++ let tee_type = aa_evidence.tee; ++ let evidence = aa_evidence.evidence.as_bytes(); ++ match tee_type { ++ #[cfg(feature = "itrustee-verifier")] ++ TeeType::Itrustee => itrustee::ItrusteeVerifier::default().evaluate(user_data, evidence).await, ++ #[cfg(feature = "virtcca-verifier")] ++ TeeType::Virtcca => virtcca::VirtCCAVerifier::default().evaluate(user_data, evidence).await, ++ _ => bail!("unsupported tee type:{:?}", tee_type), ++ } ++ } ++ async fn verify_ima(&self, ++ evidence: &[u8], ++ claim: &serde_json::Value, ++ ) -> Result<()> { ++ let aa_evidence: Evidence = serde_json::from_slice(evidence)?; ++ let tee_type = aa_evidence.tee; ++ let digest_list_file = "/etc/attestation/attestation-service/verifier/digest_list_file".to_string(); ++ match tee_type { ++ #[cfg(feature = "virtcca-verifier")] ++ TeeType::Virtcca => virtcca::ima::ImaVerify::default().ima_verify(evidence, claim, digest_list_file), ++ _ => { ++ log::info!("unsupported ima type:{:?}", tee_type); ++ Ok(()) ++ }, ++ } ++ } ++} +diff --git a/service/attestation/attestation-service/verifier/src/virtcca/ima.rs b/service/attestation/attestation-service/verifier/src/virtcca/ima.rs +new file mode 100644 +index 0000000..44292e8 +--- /dev/null ++++ b/service/attestation/attestation-service/verifier/src/virtcca/ima.rs +@@ -0,0 +1,91 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use anyhow::{Result, bail}; ++use ima_measurements::{Event, EventData, Parser}; ++use fallible_iterator::FallibleIterator; ++use std::fs; ++use std::process::Command; ++use serde_json::Value; ++use rand::Rng; ++ ++use attestation_types::{Evidence, VirtccaEvidence}; ++ ++#[derive(Debug)] ++pub struct ImaVerify { ++ log_path: String, ++} ++ ++impl Default for ImaVerify { ++ fn default() -> Self { ++ let mut rng = rand::thread_rng(); ++ let n: u64 = rng.gen(); ++ ImaVerify { ++ // log_path: format!("/tmp/attestation-service/ima-log-{}", n), // todo fs::write depends attestation-service dir exist ++ log_path: format!("/tmp/ima-log-{}", n), ++ } ++ } ++} ++ ++impl ImaVerify { ++ // todo return detail verify result list with policy ++ pub fn ima_verify(&self, evidence: &[u8], claim: &Value, digest_list_file: String) -> Result<()> { ++ let aa_evidence: Evidence = serde_json::from_slice(evidence)?; ++ let evidence = aa_evidence.evidence.as_bytes(); ++ let virtcca_ev: VirtccaEvidence = serde_json::from_slice(evidence)?; ++ let ima_log = match virtcca_ev.ima_log { ++ Some(ima_log) => ima_log, ++ _ => {log::info!("no ima log"); return Ok(())}, ++ }; ++ ++ fs::write(&self.log_path, &ima_log).expect("write img log failed"); ++ let f = fs::File::open(&self.log_path).expect("ima log file not found"); ++ ++ let claim_ima_log_hash = claim["payload"]["cvm"]["rem"][0].clone(); ++ let mut parser = Parser::new(f); ++ ++ let mut events: Vec = Vec::new(); ++ while let Some(event) = parser.next()? { ++ events.push(event); ++ } ++ ++ let pcr_values = parser.pcr_values(); ++ let pcr_10 = pcr_values.get(&10).expect("PCR 10 not measured"); ++ let string_pcr_sha256 = hex::encode(pcr_10.sha256); ++ ++ if Value::String(string_pcr_sha256.clone()) != claim_ima_log_hash { ++ log::error!("ima log verify failed string_pcr_sha256 {}, string_claim_ima_log_hash {}", string_pcr_sha256, claim_ima_log_hash); ++ bail!("ima log hash verify failed"); ++ } ++ ++ // parser each file digest in ima log, and compare with reference base value ++ for event in events { ++ let file_digest = match event.data { ++ EventData::ImaNg{digest, name} => {drop(name); digest.digest}, ++ _ => bail!("Inalid event {:?}", event), ++ }; ++ let hex_str_digest = hex::encode(file_digest); ++ //log::info!("hex_str_digest {}", hex_str_digest); ++ let output = Command::new("grep") ++ .arg("-E") ++ .arg("-i") ++ .arg(&hex_str_digest) ++ .arg(&digest_list_file) ++ .output()?; ++ if output.stdout.is_empty() { ++ log::error!("there is no refernce base value of file digest {:?}", hex_str_digest); ++ } ++ } ++ ++ Ok(()) ++ } ++} ++ +diff --git a/service/attestation/attestation-service/verifier/src/virtcca/mod.rs b/service/attestation/attestation-service/verifier/src/virtcca/mod.rs +new file mode 100644 +index 0000000..3ececb7 +--- /dev/null ++++ b/service/attestation/attestation-service/verifier/src/virtcca/mod.rs +@@ -0,0 +1,427 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++ ++//! virtcca verifier plugin ++use super::TeeClaim; ++ ++use anyhow::{Result, bail, anyhow}; ++use cose::keys::CoseKey; ++use cose::message::CoseMessage; ++use ciborium; ++use ciborium::Value; ++use openssl::rsa; ++use openssl::pkey::Public; ++use openssl::x509; ++use openssl::pkey::PKey; ++use log; ++use serde_json::json; ++ ++pub use attestation_types::VirtccaEvidence; ++pub mod ima; ++ ++#[cfg(not(feature = "no_as"))] ++const VIRTCCA_ROOT_CERT: &str = "/etc/attestation/attestation-service/verifier/virtcca/Huawei Equipment Root CA.pem"; ++#[cfg(not(feature = "no_as"))] ++const VIRTCCA_SUB_CERT: &str = "/etc/attestation/attestation-service/verifier/virtcca/Huawei IT Product CA.pem"; ++ ++// attestation agent local reference ++#[cfg(feature = "no_as")] ++const VIRTCCA_REF_VALUE_FILE: &str = "/etc/attestation/attestation-agent/local_verifier/virtcca/ref_value.json"; ++#[cfg(feature = "no_as")] ++const VIRTCCA_ROOT_CERT: &str = "/etc/attestation/attestation-agent/local_verifier/virtcca/Huawei Equipment Root CA.pem"; ++#[cfg(feature = "no_as")] ++const VIRTCCA_SUB_CERT: &str = "/etc/attestation/attestation-agent/local_verifier/virtcca/Huawei IT Product CA.pem"; ++ ++#[derive(Debug, Default)] ++pub struct VirtCCAVerifier {} ++ ++impl VirtCCAVerifier { ++ pub async fn evaluate(&self, user_data: &[u8], evidence: &[u8]) -> Result { ++ return Evidence::verify(user_data, evidence); ++ } ++} ++ ++const CBOR_TAG: u64 = 399; ++const CVM_LABEL: i128 = 44241; ++ ++const CVM_CHALLENGE_LABEL: i128 = 10; ++const CVM_RPV_LABEL: i128 = 44235; ++const CVM_RIM_LABEL: i128 = 44238; ++const CVM_REM_LABEL: i128 = 44239; ++const CVM_HASH_ALG_LABEL: i128 = 44236; ++const CVM_PUB_KEY_LABEL: i128 = 44237; ++const CVM_PUB_KEY_HASH_ALG_LABEL: i128 = 44240; ++ ++const CVM_CHALLENGE_SIZE: usize = 64; ++const CVM_RPV_SIZE: usize = 64; ++const CVM_REM_ARR_SIZE: usize = 4; ++const CVM_PUB_KEY_SIZE: usize = 550; ++ ++#[derive(Debug)] ++pub struct CvmToken { ++ pub challenge: [u8; CVM_CHALLENGE_SIZE], // 10 => bytes .size 64 ++ pub rpv: [u8; CVM_RPV_SIZE], // 44235 => bytes .size 64 ++ pub rim: Vec, // 44238 => bytes .size {32,48,64} ++ pub rem: [Vec; CVM_REM_ARR_SIZE], // 44239 => [ 4*4 bytes .size {32,48,64} ] ++ pub hash_alg: String, // 44236 => text ++ pub pub_key: [u8; CVM_PUB_KEY_SIZE], // 44237 => bytes .size 550 ++ pub pub_key_hash_alg: String, // 44240 => text ++} ++ ++pub struct Evidence { ++ /// COSE Sign1 envelope for cvm_token ++ pub cvm_envelop: CoseMessage, ++ /// Decoded cvm token ++ pub cvm_token: CvmToken, ++} ++ ++impl Evidence { ++ pub fn new() -> Self { ++ Self { ++ cvm_envelop: CoseMessage::new_sign(), ++ cvm_token: CvmToken::new(), ++ } ++ } ++ pub fn verify(user_data: &[u8], evidence: &[u8]) -> Result { ++ let virtcca_ev: VirtccaEvidence = serde_json::from_slice(evidence)?; ++ let evidence = virtcca_ev.evidence; ++ let dev_cert = virtcca_ev.dev_cert; ++ let mut evidence = Evidence::decode(evidence)?; ++ ++ // verify platform token ++ evidence.verify_platform_token(&dev_cert)?; ++ ++ // verify cvm token ++ evidence.verify_cvm_token(user_data)?; ++ ++ // todo parsed TeeClaim ++ evidence.parse_claim_from_evidence() ++ } ++ fn parse_claim_from_evidence(&self) -> Result { ++ let payload = json!({ ++ "vcca.cvm.challenge": hex::encode(self.cvm_token.challenge.clone()), ++ "vcca.cvm.rpv": hex::encode(self.cvm_token.rpv.clone()), ++ "vcca.cvm.rim": hex::encode(self.cvm_token.rim.clone()), ++ "vcca.cvm.rem.0": hex::encode(self.cvm_token.rem[0].clone()), ++ "vcca.cvm.rem.1": hex::encode(self.cvm_token.rem[1].clone()), ++ "vcca.cvm.rem.2": hex::encode(self.cvm_token.rem[2].clone()), ++ "vcca.cvm.rem.3": hex::encode(self.cvm_token.rem[3].clone()), ++ "vcca.platform": "", ++ }); ++ let claim = json!({ ++ "tee": "vcca", ++ "payload" : payload, ++ }); ++ Ok(claim as TeeClaim) ++ } ++ fn verify_platform_token(&mut self, dev_cert: &[u8]) -> Result<()> { ++ // todo verify platform COSE_Sign1 by dev_cert, virtCCA report has no platform token now ++ ++ // verify dev_cet by cert chain ++ Evidence::verify_dev_cert_chain(dev_cert)?; ++ ++ Ok(()) ++ } ++ // todo verify cert chain, now only verify signature ++ fn verify_dev_cert_chain(dev_cert: &[u8]) -> Result<()> { ++ let dev_cert = x509::X509::from_der(dev_cert)?; ++ let sub_cert_file = std::fs::read(VIRTCCA_SUB_CERT)?; ++ let sub_cert = x509::X509::from_pem(&sub_cert_file)?; ++ let root_cert_file = std::fs::read(VIRTCCA_ROOT_CERT)?; ++ let root_cert = x509::X509::from_pem(&root_cert_file)?; ++ ++ // verify dev_cert by sub_cert ++ let ret = dev_cert.verify(&(sub_cert.public_key()? as PKey))?; ++ if !ret { ++ log::error!("verify dev cert by sub cert failed"); ++ bail!("verify dev cert by sub cert failed"); ++ } ++ // verify sub_cert by root_cert ++ let ret = sub_cert.verify(&(root_cert.public_key()? as PKey))?; ++ if !ret { ++ log::error!("verify sub cert by root cert failed"); ++ bail!("verify sub cert by root cert failed"); ++ } ++ // verify self signed root_cert ++ let ret = root_cert.verify(&(root_cert.public_key()? as PKey))?; ++ if !ret { ++ log::error!("verify self signed root cert failed"); ++ bail!("verify self signed root cert failed"); ++ } ++ Ok(()) ++ } ++ fn verify_cvm_token(&mut self, challenge: &[u8]) -> Result<()> { ++ // verify challenge ++ let len = challenge.len(); ++ let token_challenge = &self.cvm_token.challenge[0..len]; ++ if challenge != token_challenge { ++ log::error!("verify cvm token challenge error, cvm_token challenge {:?}, input challenge {:?}", ++ token_challenge, challenge); ++ bail!("verify cvm token challenge error, cvm_token challenge {:?}, input challenge {:?}", ++ token_challenge, challenge); ++ } ++ ++ // todo verify cvm pubkey by platform.challenge, virtCCA report has no platform token now ++ ++ // verify COSE_Sign1 signature begin ++ let raw_pub_key = self.cvm_token.pub_key; ++ let mut cose_key: CoseKey = Evidence::from_raw_pub_key(&raw_pub_key)?; ++ cose_key.key_ops(vec![cose::keys::KEY_OPS_VERIFY]); ++ match self.cvm_envelop.header.alg { ++ Some(alg) => cose_key.alg(alg), ++ None => bail!("cose sign verify alg is none"), ++ } ++ self.cvm_envelop.key(&cose_key).map_err(|err| anyhow!("set cose_key to COSE_Sign1 envelop failed: {err:?}"))?; ++ self.cvm_envelop.decode(None, None).map_err(|err| anyhow!("verify COSE_Sign1 signature failed:{err:?}"))?; ++ // verify COSE_Sign1 signature end ++ ++ // verfiy cvm token with reference value ++ #[cfg(feature = "no_as")] ++ self.compare_with_ref()?; ++ ++ Ok(()) ++ } ++ #[cfg(feature = "no_as")] ++ fn compare_with_ref(&mut self) -> Result<()> { ++ let ref_file = std::fs::read(VIRTCCA_REF_VALUE_FILE)?; ++ let js_ref = serde_json::from_slice(&ref_file)?; ++ match js_ref { ++ serde_json::Value::Object(obj) => { ++ for (k, v) in obj { ++ if k == "rim" { ++ let rim_ref = match v { ++ serde_json::Value::String(rim) => rim, ++ _ => bail!("tim ref expecting String"), ++ }; ++ let rim = hex::encode(self.cvm_token.rim.clone()); ++ if rim_ref != rim { ++ log::error!("expecting rim: {}, got: {}", rim_ref, rim); ++ bail!("expecting rim: {}, got: {}", rim_ref, rim); ++ } ++ } ++ } ++ } ++ _ => bail!("invalid json ref value"), ++ } ++ ++ Ok(()) ++ } ++ fn from_raw_pub_key(raw_pub_key: &[u8]) -> Result { ++ let pub_key: rsa::Rsa = rsa::Rsa::public_key_from_der(raw_pub_key)?; ++ let mut cose_key = CoseKey::new(); ++ cose_key.kty(cose::keys::RSA); ++ cose_key.e(pub_key.e().to_vec()); ++ cose_key.n(pub_key.n().to_vec()); ++ ++ Ok(cose_key) ++ } ++ pub fn decode(raw_evidence: Vec) -> Result { ++ let mut evidence: Evidence = Evidence::new(); ++ ++ // decode CBOR evidence to ciborium Value ++ let val: Value = ciborium::de::from_reader(raw_evidence.as_slice())?; ++ log::debug!("[debug] decode CBOR virtcca token to ciborium Value:{:?}", val); ++ if let Value::Tag(t, m) = val { ++ if t != CBOR_TAG { ++ log::error!("input evidence error, expecting tag {}, got {}", CBOR_TAG, t); ++ bail!("input evidence error, expecting tag {}, got {}", CBOR_TAG, t); ++ } ++ if let Value::Map(contents) = *m { ++ for (k, v) in contents.iter() { ++ if let Value::Integer(i) = k { ++ match (*i).into() { ++ CVM_LABEL => evidence.set_cvm_token(v)?, ++ err => bail!("unknown label {}", err), ++ } ++ } else { ++ bail!("expecting integer key"); ++ } ++ } ++ } else { ++ bail!("expecting map type"); ++ } ++ } else { ++ bail!("expecting tag type"); ++ } ++ ++ let ret = evidence.cvm_envelop.init_decoder(None); ++ match ret { ++ Ok(_) => log::info!("decode COSE success"), ++ Err(e) => { ++ log::error!("decode COSE failed, {:?}", e); ++ bail!("decode COSE failed"); ++ }, ++ } ++ ++ // decode cvm CBOR payload ++ evidence.cvm_token = CvmToken::decode(&evidence.cvm_envelop.payload)?; ++ Ok(evidence) ++ } ++ fn set_cvm_token(&mut self, v: &Value) -> Result<()> { ++ let tmp = v.as_bytes(); ++ if tmp.is_none() { ++ log::error!("cvm token is none"); ++ bail!("cvm token is none"); ++ } ++ self.cvm_envelop.bytes = tmp.unwrap().clone(); ++ Ok(()) ++ } ++} ++ ++impl CvmToken { ++ pub fn new() -> Self { ++ Self { ++ challenge: [0; CVM_CHALLENGE_SIZE], ++ rpv: [0; CVM_RPV_SIZE], ++ rim: vec![0, 64], ++ rem: Default::default(), ++ hash_alg: String::from(""), ++ pub_key: [0; CVM_PUB_KEY_SIZE], ++ pub_key_hash_alg: String::from(""), ++ } ++ } ++ pub fn decode(raw_payload: &Vec) -> Result { ++ let payload: Vec = ciborium::de::from_reader(raw_payload.as_slice())?; ++ log::debug!("After decode CBOR payload, payload {:?}", payload); ++ let payload: Value = ciborium::de::from_reader(payload.as_slice())?; ++ log::debug!("After decode CBOR payload agin, payload {:?}", payload); ++ let mut cvm_token: CvmToken = CvmToken::new(); ++ if let Value::Map(contents) = payload { ++ for (k, v) in contents.iter() { ++ if let Value::Integer(i) = k { ++ match (*i).into() { ++ CVM_CHALLENGE_LABEL => cvm_token.set_challenge(v)?, ++ CVM_RPV_LABEL => cvm_token.set_rpv(v)?, ++ CVM_RIM_LABEL => cvm_token.set_rim(v)?, ++ CVM_REM_LABEL => cvm_token.set_rem(v)?, ++ CVM_HASH_ALG_LABEL => cvm_token.set_hash_alg(v)?, ++ CVM_PUB_KEY_LABEL => cvm_token.set_pub_key(v)?, ++ CVM_PUB_KEY_HASH_ALG_LABEL => cvm_token.set_pub_key_hash_alg(v)?, ++ err => bail!("cvm payload unkown label {}", err), ++ } ++ } else { ++ bail!("cvm payload expecting integer key"); ++ } ++ } ++ } else { ++ bail!("expecting cvm payload map type"); ++ } ++ log::debug!("cvm_token decode from raw payload, {:?}", cvm_token); ++ Ok(cvm_token) ++ } ++ fn set_challenge(&mut self, v: &Value) -> Result<()> { ++ let tmp = v.as_bytes(); ++ if tmp.is_none() { ++ bail!("cvm token challenge is none"); ++ } ++ let tmp = tmp.unwrap().clone(); ++ if tmp.len() != CVM_CHALLENGE_SIZE { ++ bail!("cvm token challenge expecting {} bytes, got {}", CVM_CHALLENGE_SIZE,tmp.len()); ++ } ++ self.challenge[..].clone_from_slice(&tmp); ++ Ok(()) ++ } ++ fn set_rpv(&mut self, v: &Value) -> Result<()> { ++ let tmp = v.as_bytes(); ++ if tmp.is_none() { ++ bail!("cvm token rpv is none"); ++ } ++ let tmp = tmp.unwrap().clone(); ++ if tmp.len() != CVM_RPV_SIZE { ++ bail!("cvm token rpv expecting {} bytes, got {}", CVM_RPV_SIZE, tmp.len()); ++ } ++ self.rpv[..].clone_from_slice(&tmp); ++ Ok(()) ++ } ++ fn get_measurement(v: &Value, who: &str) -> Result> { ++ let tmp = v.as_bytes(); ++ if tmp.is_none() { ++ bail!("cvm token {} is none", who); ++ } ++ let tmp = tmp.unwrap().clone(); ++ if !matches!(tmp.len(), 32 | 48 | 64) { ++ bail!("cvm token {} expecting 32, 48 or 64 bytes, got {}", who, tmp.len()); ++ } ++ Ok(tmp) ++ } ++ fn set_rim(&mut self, v: &Value) -> Result<()> { ++ self.rim = Self::get_measurement(v, "rim")?; ++ Ok(()) ++ } ++ fn set_rem(&mut self, v: &Value) -> Result<()> { ++ let tmp = v.as_array(); ++ if tmp.is_none() { ++ bail!("cvm token rem is none"); ++ } ++ let tmp = tmp.unwrap().clone(); ++ if tmp.len() != 4 { ++ bail!("cvm token rem expecting size {}, got {}", CVM_REM_ARR_SIZE, tmp.len()); ++ } ++ ++ for (i, val) in tmp.iter().enumerate() { ++ self.rem[i] = Self::get_measurement(val, "rem[{i}]")?; ++ } ++ Ok(()) ++ } ++ fn get_hash_alg(v: &Value, who: &str) -> Result { ++ let alg = v.as_text(); ++ if alg.is_none() { ++ bail!("{} hash alg must be str", who); ++ } ++ Ok(alg.unwrap().to_string()) ++ } ++ fn set_hash_alg(&mut self, v: &Value) -> Result<()> { ++ self.hash_alg = Self::get_hash_alg(v, "cvm token")?; ++ Ok(()) ++ } ++ fn set_pub_key(&mut self, v: &Value) -> Result<()> { ++ let tmp = v.as_bytes(); ++ if tmp.is_none() { ++ bail!("cvm token pub key is none"); ++ } ++ let tmp = tmp.unwrap().clone(); ++ if tmp.len() != CVM_PUB_KEY_SIZE { ++ bail!("cvm token pub key len expecting {}, got {}", CVM_PUB_KEY_SIZE, tmp.len()); ++ } ++ self.pub_key[..].clone_from_slice(&tmp); ++ Ok(()) ++ } ++ fn set_pub_key_hash_alg(&mut self, v: &Value) -> Result<()> { ++ self.pub_key_hash_alg = Self::get_hash_alg(v, "pub key")?; ++ Ok(()) ++ } ++} ++ ++#[cfg(test)] ++mod tests { ++ use super::*; ++ use hex; ++ ++ const TEST_VIRTCCA_TOKEN: &[u8; 2862] = include_bytes!("../../test_data/virtcca.cbor"); ++ #[test] ++ fn decode_token() { ++ let token = hex::decode(TEST_VIRTCCA_TOKEN).unwrap(); ++ let dev_cert = std::fs::read("./test_data/virtcca_aik_cert.der").unwrap(); ++ let challenge = Vec::new(); ++ let virtcca_ev = VirtccaEvidence { ++ evidence: token.to_vec(), ++ dev_cert: dev_cert, ++ ima_log: None, ++ }; ++ let virtcca_ev = serde_json::to_vec(&virtcca_ev).unwrap(); ++ let r = Evidence::verify(&challenge, &virtcca_ev); ++ match r { ++ Ok(claim) => println!("verify success {:?}", claim), ++ Err(e) => assert!(false, "verify failed {:?}", e), ++ } ++ } ++} +diff --git a/service/attestation/attestation-types/Cargo.toml b/service/attestation/attestation-types/Cargo.toml +new file mode 100644 +index 0000000..1fcf465 +--- /dev/null ++++ b/service/attestation/attestation-types/Cargo.toml +@@ -0,0 +1,8 @@ ++[package] ++name = "attestation-types" ++version = "0.1.0" ++edition = "2021" ++ ++[dependencies] ++serde = { version = "1.0", features = ["derive"] } ++serde_json = "1.0" +\ No newline at end of file +diff --git a/service/attestation/attestation-types/src/lib.rs b/service/attestation/attestation-types/src/lib.rs +new file mode 100644 +index 0000000..fcf1d3e +--- /dev/null ++++ b/service/attestation/attestation-types/src/lib.rs +@@ -0,0 +1,52 @@ ++/* ++ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. ++ * secGear is licensed under the Mulan PSL v2. ++ * You can use this software according to the terms and conditions of the Mulan PSL v2. ++ * You may obtain a copy of Mulan PSL v2 at: ++ * http://license.coscl.org.cn/MulanPSL2 ++ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR ++ * PURPOSE. ++ * See the Mulan PSL v2 for more details. ++ */ ++use serde::{Serialize, Deserialize}; ++use serde_json::Value; ++ ++#[derive(Debug, Serialize, Deserialize)] ++pub struct VirtccaEvidence { ++ pub evidence: Vec, ++ pub dev_cert: Vec, ++ pub ima_log: Option>, ++} ++ ++#[derive(Debug, Serialize, Deserialize)] ++pub enum TeeType { ++ Itrustee = 1, ++ Virtcca, ++ Invalid, ++} ++ ++ ++#[derive(Debug, Serialize, Deserialize)] ++pub struct Evidence { ++ pub tee: TeeType, ++ pub evidence: String, ++} ++ ++#[derive(Debug, Clone, Serialize, Deserialize)] ++pub struct EvlResult { ++ pub eval_result: bool, ++ pub policy: Vec, ++ pub report: Value, ++} ++ ++#[derive(Debug, Clone, Serialize, Deserialize)] ++pub struct Claims { ++ pub iss: String, ++ pub iat: usize, ++ pub nbf: usize, ++ pub exp: usize, ++ pub evaluation_reports: EvlResult, ++ pub tee: String, ++ pub tcb_status: Value, ++} +\ No newline at end of file +-- +2.33.0 + diff --git a/secGear.spec b/secGear.spec index bef5cf3..77b0d21 100644 --- a/secGear.spec +++ b/secGear.spec @@ -1,6 +1,6 @@ Name: secGear Version: 0.1.0 -Release: 43 +Release: 44 Summary: secGear is an SDK to develop confidential computing apps based on hardware enclave features @@ -88,7 +88,7 @@ Patch75: 0076-change-log-file-permission-0400.patch Patch76: 0077-support-CPU-core-binding.patch Patch77: 0078-register-shared-memory-by-open-session.patch Patch78: 0079-Optimize-the-registration-shared-memory.patch - +Patch79: 0080-add-attestation-service.patch BuildRequires: gcc python automake autoconf libtool BUildRequires: glibc glibc-devel cmake ocaml-dune rpm gcc-c++ compat-openssl11-libs compat-openssl11-devel @@ -220,6 +220,9 @@ popd systemctl restart rsyslog %changelog +* Fri Nov 8 2024 steven - 0.1.0-44 +- backport patches from 2409 + * Fri Oct 25 2024 houmingyong - 0.1.0-43 - add rust third party dependencies -- Gitee