diff --git a/0089-features-support-resource-maitainance.patch b/0089-features-support-resource-maitainance.patch new file mode 100644 index 0000000000000000000000000000000000000000..09e4dcd4a4622e475c2d2533ea04e40187532e70 --- /dev/null +++ b/0089-features-support-resource-maitainance.patch @@ -0,0 +1,7140 @@ +From f1938d334b93ffb2c6b6f1f210767d95ea642243 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Mon, 17 Feb 2025 20:09:31 +0800 +Subject: [PATCH 1/1] support resource matainance + +--- + service/attestation/README.md | 177 ++++++++ + .../attestation/attestation-agent/Cargo.toml | 14 +- + .../attestation-agent/agent/Cargo.toml | 3 +- + .../agent/attestation-agent.conf | 11 +- + .../agent/src/bin/aa-test/main.rs | 88 +++- + .../attestation-agent/agent/src/lib.rs | 380 +++++++++++++----- + .../attestation-agent/agent/src/main.rs | 65 ++- + .../agent/src/restapi/mod.rs | 157 ++++++-- + .../attestation-agent/agent/src/result/mod.rs | 3 + + .../attestation-agent/agent/src/session.rs | 13 +- + .../attestation-agent/attester/Cargo.toml | 4 +- + .../attester/src/itrustee/mod.rs | 26 +- + .../attestation-agent/attester/src/lib.rs | 32 +- + .../attester/src/virtcca/mod.rs | 37 +- + .../attester/src/virtcca/virtcca.rs | 6 +- + .../attestation-agent/c_header/example.c | 90 +++++ + .../c_header/rust_attestation_agent.h | 81 ++++ + .../attestation-agent/token/Cargo.toml | 2 +- + .../attestation-agent/token/src/lib.rs | 41 +- + .../attestation/attestation-client/Cargo.toml | 16 + + .../attestation-client/src/client.rs | 53 +++ + .../attestation-client/src/error.rs | 23 ++ + .../attestation-client/src/main.rs | 52 +++ + .../attestation-client/src/resource/client.rs | 234 +++++++++++ + .../attestation-client/src/resource/mod.rs | 129 ++++++ + .../src/resource_policy/client.rs | 190 +++++++++ + .../src/resource_policy/mod.rs | 100 +++++ + .../attestation-service/Cargo.toml | 17 +- + .../attestation/attestation-service/README.md | 19 +- + .../attestation-service/as_startup.sh | 138 +++++++ + .../attestation-service/policy/Cargo.toml | 1 + + .../attestation-service/policy/src/lib.rs | 8 +- + .../attestation-service/policy/src/opa/mod.rs | 65 ++- + .../attestation-service/reference/Cargo.toml | 2 +- + .../attestation-service/reference/src/lib.rs | 19 +- + .../reference/src/local_fs/mod.rs | 6 +- + .../reference/src/reference/mod.rs | 2 +- + .../reference/src/store/mod.rs | 2 +- + .../attestation-service/service/Cargo.toml | 5 +- + .../attestation-service/service/src/lib.rs | 175 +++++--- + .../attestation-service/service/src/main.rs | 76 ++-- + .../service/src/restapi/mod.rs | 141 ++++--- + .../service/src/restapi/resource/mod.rs | 14 + + .../service/src/restapi/resource/policy.rs | 116 ++++++ + .../service/src/restapi/resource/storage.rs | 158 ++++++++ + .../service/src/result/mod.rs | 17 +- + .../service/src/session.rs | 32 +- + .../attestation-service/tests/Cargo.toml | 2 +- + .../attestation-service/tests/src/lib.rs | 6 +- + .../attestation-service/token/Cargo.toml | 2 +- + .../attestation-service/token/src/lib.rs | 60 ++- + .../attestation-service/verifier/Cargo.toml | 6 +- + .../verifier/src/itrustee/mod.rs | 35 +- + .../attestation-service/verifier/src/lib.rs | 41 +- + .../verifier/src/rustcca/LICENSE | 201 +++++++++ + .../verifier/src/rustcca/mod.rs | 246 ++++++++++++ + .../verifier/src/virtcca/ima.rs | 44 +- + .../verifier/src/virtcca/mod.rs | 152 +++++-- + .../attestation/attestation-types/Cargo.toml | 11 +- + .../attestation/attestation-types/src/lib.rs | 10 +- + .../src/resource/admin/mod.rs | 59 +++ + .../src/resource/admin/simple.rs | 256 ++++++++++++ + .../attestation-types/src/resource/error.rs | 45 +++ + .../attestation-types/src/resource/mod.rs | 145 +++++++ + .../src/resource/policy/mod.rs | 128 ++++++ + .../src/resource/policy/opa/mod.rs | 321 +++++++++++++++ + .../src/resource/policy/opa/virtcca.rego | 12 + + .../src/resource/storage/mod.rs | 67 +++ + .../src/resource/storage/simple.rs | 220 ++++++++++ + .../attestation-types/src/resource/utils.rs | 32 ++ + .../attestation-types/src/service.rs | 83 ++++ + 71 files changed, 4697 insertions(+), 527 deletions(-) + create mode 100644 service/attestation/README.md + create mode 100644 service/attestation/attestation-agent/c_header/example.c + create mode 100644 service/attestation/attestation-agent/c_header/rust_attestation_agent.h + create mode 100644 service/attestation/attestation-client/Cargo.toml + create mode 100644 service/attestation/attestation-client/src/client.rs + create mode 100644 service/attestation/attestation-client/src/error.rs + create mode 100644 service/attestation/attestation-client/src/main.rs + create mode 100644 service/attestation/attestation-client/src/resource/client.rs + create mode 100644 service/attestation/attestation-client/src/resource/mod.rs + create mode 100644 service/attestation/attestation-client/src/resource_policy/client.rs + create mode 100644 service/attestation/attestation-client/src/resource_policy/mod.rs + create mode 100755 service/attestation/attestation-service/as_startup.sh + create mode 100644 service/attestation/attestation-service/service/src/restapi/resource/mod.rs + create mode 100644 service/attestation/attestation-service/service/src/restapi/resource/policy.rs + create mode 100644 service/attestation/attestation-service/service/src/restapi/resource/storage.rs + create mode 100644 service/attestation/attestation-service/verifier/src/rustcca/LICENSE + create mode 100644 service/attestation/attestation-service/verifier/src/rustcca/mod.rs + create mode 100644 service/attestation/attestation-types/src/resource/admin/mod.rs + create mode 100644 service/attestation/attestation-types/src/resource/admin/simple.rs + create mode 100644 service/attestation/attestation-types/src/resource/error.rs + create mode 100644 service/attestation/attestation-types/src/resource/mod.rs + create mode 100644 service/attestation/attestation-types/src/resource/policy/mod.rs + create mode 100644 service/attestation/attestation-types/src/resource/policy/opa/mod.rs + create mode 100644 service/attestation/attestation-types/src/resource/policy/opa/virtcca.rego + create mode 100644 service/attestation/attestation-types/src/resource/storage/mod.rs + create mode 100644 service/attestation/attestation-types/src/resource/storage/simple.rs + create mode 100644 service/attestation/attestation-types/src/resource/utils.rs + create mode 100644 service/attestation/attestation-types/src/service.rs + +diff --git a/service/attestation/README.md b/service/attestation/README.md +new file mode 100644 +index 0000000..32bfd42 +--- /dev/null ++++ b/service/attestation/README.md +@@ -0,0 +1,177 @@ ++# Attestation ++This project provides attestation service and attestation agent for common attestation scenes. ++ ++## Components ++- Attestation Agent: An agent depends by relying party or attester for attestation. ++- Attestation Service: A verifier verifies TEE evidence. ++ ++Note: The roles relying party, attester and verifier is defined in [RFC9334 RATS](https://datatracker.ietf.org/doc/html/rfc9334#name-architectural-overview). ++ ++# Quick Start ++## Prepare ++### Install attester depends SDK ++- OS: openEuler 24.09 ++- Repo ++``` ++vim /etc/yum.repos.d/openEuler.repo ++[everything] ++name=everything ++baseurl=https://repo.openeuler.org/openEuler-24.09/everything/aarch64/ ++enabled=1 ++gpgcheck=0 ++ ++yum install virtCCA_sdk-devel ++``` ++ ++### Generate self-signed certificate and private key ++``` ++openssl genrsa -out private.pem 2048 ++openssl req -new -key private.pem -out server.csr ++openssl x509 -req -in server.csr -out as_cert.pem -signkey private.pem -days 3650 ++ ++mkdir -p /etc/attestation/attestation-service/token ++cp private.pem /etc/attestation/attestation-service/token ++ ++// as_cert.pem will be deployed into AA config directory, AA use it to verify token ++``` ++ ++### Generate AS Config File ++``` ++mkdir -p /etc/attestation/attestation-service/ ++vim /etc/attestation.bak/attestation-service/attestation-service.conf ++{ ++ "token_cfg": { ++ "key": "/etc/attestation/attestation-service/token/private.pem", ++ "iss": "oeas", ++ "nbf": 0, ++ "valid_duration": 300, ++ "alg": "PS256" ++ } ++} ++``` ++ ++### Generate AA Config File ++``` ++mkdir -p /etc/attestation/attestation-agent/ ++// svr_url: url to access attestation service ++// cert: token signature certificate ++// iss: token issuer name ++vim /etc/attestation/attestation-agent/attestation-agent.conf ++{ ++ "svr_url": "http://127.0.0.1:8080", ++ "token_cfg": { ++ "cert": "/etc/attestation/attestation-agent/as_cert.pem", ++ "iss": "oeas" ++ } ++} ++``` ++ ++### Download Huawei root cert chain to verify virtCCA evidence ++ ++[Root Cert](https://download.huawei.com/dl/download.do?actionFlag=download&nid=PKI1000000002&partNo=3001&mid=SUP_PKI)
++[Sub Cert](https://download.huawei.com/dl/download.do?actionFlag=download&nid=PKI1000000040&partNo=3001&mid=SUP_PKI) ++``` ++mkdir -p /etc/attestation/attestation-service/verifier/virtcca ++// copy Root Cert and Sub Cert to the above directory ++``` ++ ++## Build ++ ++### Build AA ++``` ++cd secGear/service/attestation/attestation-agent ++cargo build --features virtcca-attester ++``` ++ ++### Build AS ++``` ++cd secGear/service/attestation/attestation-service ++cargo build ++``` ++ ++## Run AS, AA and Demo(aa-test)Use HTTP ++### Run AS ++``` ++cd secGear/service/attestation/attestation-service ++./target/debug/attestation-service ++``` ++attestation service listens on 127.0.0.1:8080 default, also can specify custom ip:port by -s param such as ++``` ++./target/debug/attestation-service -s ip:port ++``` ++ ++- Config default policy ++``` ++cp secGear/service/attestation/attestation-service/policy/src/opa/default_vcca.rego /etc/attestation/attestation-service/policy ++``` ++- Config virtcca reference ++ ++virtcca reference (such as rim:7d2e49c8d29f18b748e658e7243ecf26bc292e5fee93f72af11ad9da9810142a ) is generated by [rim_ref tools](https://gitee.com/openeuler/virtCCA_sdk/tree/master/attestation/rim_ref) ++``` ++curl -H "Content-Type:application/json" -X POST -d '{"refs":"{\"vcca.cvm.rim\":\"7d2e49c8d29f18b748e658e7243ecf26bc292e5fee93f72af11ad9da9810142a\"}"}' http://127.0.0.1:8080/reference ++``` ++ ++### Run AA ++ ++#### Run AA ++``` ++cd secGear/service/attestation/attestation-agent ++./target/debug/attestation-agent ++``` ++ ++### Run AA demo ++``` ++cd secGear/service/attestation/attestation-agent ++./target/debug/aa-test ++``` ++ ++## Run AS, AA and Demo(aa-test)Use HTTPS ++### Run AS ++- generate self-signed certificate ++``` ++openssl genrsa -out key.pem 2048 ++openssl req -subj "/C=CN/ST=ST/L=CITY/O=Company/CN=server.com" -new -key key.pem -out cert.csr ++openssl x509 -req -extfile /etc/pki/tls/openssl.cnf -extensions v3_req -in cert.csr -out cert.pem -signkey key.pem -days 365 ++``` ++ ++- config hosts ++``` ++vim /etc/hosts ++127.0.0.1 server.com ++``` ++ ++- start service ++``` ++cd secGear/service/attestation/attestation-service ++./target/debug/attestation-service -p https -t cert.pem -k key.pem 2>&1 & ++// you can specified listen port ++./target/debug/attestation-service -p https -t cert.pem -k key.pem -s server.com:8080 2>&1 & ++``` ++ ++- Config default policy ++``` ++cp secGear/service/attestation/attestation-service/policy/src/opa/default_vcca.rego /etc/attestation/attestation-service/policy ++``` ++ ++- Config virtcca reference ++ ++virtcca reference (such as rim:7d2e49c8d29f18b748e658e7243ecf26bc292e5fee93f72af11ad9da9810142a ) is generated by [rim_ref tools](https://gitee.com/openeuler/virtCCA_sdk/tree/master/attestation/rim_ref) ++``` ++curl -k -H "Content-Type:application/json" -X POST -d '{"refs":"{\"vcca.cvm.rim\":\"7d2e49c8d29f18b748e658e7243ecf26bc292e5fee93f72af11ad9da9810142a\"}"}' https://server.com:8080/reference ++``` ++ ++### Run AA ++ ++``` ++cd secGear/service/attestation/attestation-agent ++./target/debug/attestation-agent -p https -t cert.pem 2>&1 & ++ ++// you can use -u specified destination which AA connect to , -s specified port which AA listen at ++./target/debug/attestation-agent -p https -t cert.pem -s server.com:8081 -u server.com:8080 2>&1 & ++ ++``` ++### Run AA demo ++``` ++cd secGear/service/attestation/attestation-agent ++./target/debug/aa-test ++``` +\ No newline at end of file +diff --git a/service/attestation/attestation-agent/Cargo.toml b/service/attestation/attestation-agent/Cargo.toml +index f6f31b1..126a9f4 100644 +--- a/service/attestation/attestation-agent/Cargo.toml ++++ b/service/attestation/attestation-agent/Cargo.toml +@@ -1,10 +1,6 @@ + [workspace] + resolver = "2" +-members = [ +- "agent", +- "attester", +- "token" +-] ++members = ["agent", "attester", "token"] + + [workspace.dependencies] + anyhow = "1.0" +@@ -14,10 +10,10 @@ serde_json = "1.0" + rand = "0.8.5" + base64-url = "3.0.0" + async-trait = "0.1.78" +-tokio = {version = "1.0", features = ["rt"]} ++tokio = { version = "1.0", features = ["rt"] } + log = "0.4.14" + env_logger = "0.9" +-safer-ffi = {version = "0.1.8", features = ["alloc"]} ++safer-ffi = { version = "0.1.8", features = ["alloc"] } + futures = "0.3.30" + reqwest = { version = "0.12", features = ["cookies", "json"] } + jsonwebtoken = "9.3.0" +@@ -26,5 +22,5 @@ actix-web = "4.5" + clap = { version = "4.5.7", features = ["derive"] } + scc = "2.1" + +-verifier = {path = "../attestation-service/verifier", default-features = false} +-attestation-types = {path = "../attestation-types"} ++verifier = { path = "../attestation-service/verifier", default-features = false } ++attestation-types = { path = "../attestation-types" } +diff --git a/service/attestation/attestation-agent/agent/Cargo.toml b/service/attestation/attestation-agent/agent/Cargo.toml +index d2450c8..07c1c01 100644 +--- a/service/attestation/attestation-agent/agent/Cargo.toml ++++ b/service/attestation/attestation-agent/agent/Cargo.toml +@@ -36,13 +36,14 @@ log.workspace = true + env_logger.workspace = true + safer-ffi.workspace = true + futures.workspace = true +-reqwest = { workspace = true, features = ["json"] } ++reqwest = { workspace = true, features = ["json", "cookies"] } + base64-url.workspace = true + thiserror.workspace = true + actix-web.workspace = true + clap.workspace = true + scc.workspace = true + attestation-types.workspace = true ++jsonwebtoken.workspace = true + + attester = { path = "../attester" } + token_verifier = { path = "../token" } +diff --git a/service/attestation/attestation-agent/agent/attestation-agent.conf b/service/attestation/attestation-agent/agent/attestation-agent.conf +index 5c9a015..76ae4d1 100644 +--- a/service/attestation/attestation-agent/agent/attestation-agent.conf ++++ b/service/attestation/attestation-agent/agent/attestation-agent.conf +@@ -1,7 +1,12 @@ + { + "svr_url": "http://127.0.0.1:8080", + "token_cfg": { +- "cert": "/etc/attestation/attestation-agent/as_cert.pem", +- "iss": "oeas" ++ "cert": "/etc/attestation/attestation-agent/as_cert.pem", ++ "iss": "oeas" ++ }, ++ "protocal": { ++ "Http": { ++ "protocal": "http" ++ } + } +-} ++} +\ 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 +index 4867a23..8aa2200 100644 +--- a/service/attestation/attestation-agent/agent/src/bin/aa-test/main.rs ++++ b/service/attestation/attestation-agent/agent/src/bin/aa-test/main.rs +@@ -12,19 +12,22 @@ + + //! 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; ++use serde_json::json; ++use tokio; + + const TEST_THREAD_NUM: i64 = 1; // multi thread num ++const AA_ADDR: &str = "http://127.0.0.1:8081"; + + #[tokio::main] + async fn main() { + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); + 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;}); ++ let t = tokio::spawn(async move { ++ aa_proc(i).await; ++ }); + handles.push(t); + } + +@@ -40,7 +43,7 @@ async fn aa_proc(i: i64) { + // get challenge + log::info!("thread {} case1 get challenge", i); + let client = reqwest::Client::new(); +- let challenge_endpoint = "http://127.0.0.1:8081/challenge"; ++ let challenge_endpoint = format!("{AA_ADDR}/challenge"); + let res = client + .get(challenge_endpoint) + .header("Content-Type", "application/json") +@@ -53,11 +56,19 @@ async fn aa_proc(i: i64) { + let challenge = match res.status() { + reqwest::StatusCode::OK => { + let respone = res.text().await.unwrap(); +- log::info!("thread {} case1 get challenge success response: {:?}", i, respone); ++ log::info!( ++ "thread {} case1 get challenge success response: {:?}", ++ i, ++ respone ++ ); + respone + } + status => { +- log::error!("thread {} case1 get challenge failed response: {:?}", i, status); ++ log::error!( ++ "thread {} case1 get challenge failed response: {:?}", ++ i, ++ status ++ ); + return; + } + }; +@@ -67,11 +78,15 @@ async fn aa_proc(i: i64) { + "challenge": challenge, + "uuid": String::from("f68fd704-6eb1-4d14-b218-722850eb3ef0"), + }); +- log::info!("thread {} case2 get evidence, request body: {}", i, request_body); +- let attest_endpoint = "http://127.0.0.1:8081/evidence"; ++ log::info!( ++ "thread {} case2 get evidence, request body: {}", ++ i, ++ request_body ++ ); ++ let attest_endpoint = format!("{AA_ADDR}/evidence"); + let client = reqwest::Client::new(); + let res = client +- .get(attest_endpoint) ++ .get(attest_endpoint.clone()) + .header("Content-Type", "application/json") + .json(&request_body) + .send() +@@ -86,7 +101,11 @@ async fn aa_proc(i: i64) { + respone + } + status => { +- log::error!("thread {} case2 get evidence failed response: {:?}", i, status); ++ log::error!( ++ "thread {} case2 get evidence failed response: {:?}", ++ i, ++ status ++ ); + return; + } + }; +@@ -109,25 +128,37 @@ async fn aa_proc(i: i64) { + match res.status() { + reqwest::StatusCode::OK => { + let respone = res.text().await.unwrap(); +- log::info!("thread {} case4 verify evidence success response: {:?}", i, respone); ++ log::info!( ++ "thread {} case4 verify evidence success response: {:?}", ++ i, ++ respone ++ ); + } + status => { +- log::error!("thread {} case4 verify evidence failed response: {:?}", i, status); ++ log::error!( ++ "thread {} case4 verify evidence failed response: {:?}", ++ i, ++ status ++ ); + } + } + + #[cfg(not(feature = "no_as"))] + { + // get token +- let token_endpoint = "http://127.0.0.1:8081/token"; ++ let token_endpoint = format!("{AA_ADDR}/token"); + let request_body = json!({ + "challenge": challenge, + "uuid": String::from("f68fd704-6eb1-4d14-b218-722850eb3ef0"), + }); +- log::info!("thread {} case5 get token, request body: {}", i, request_body); ++ log::info!( ++ "thread {} case5 get token, request body: {}", ++ i, ++ request_body ++ ); + let client = reqwest::Client::new(); + let res = client +- .get(token_endpoint) ++ .get(token_endpoint.clone()) + .header("Content-Type", "application/json") + .json(&request_body) + .send() +@@ -142,7 +173,12 @@ async fn aa_proc(i: i64) { + respone + } + status => { +- log::error!("thread {} case5 get token failed status: {:?} response: {:?}", i, status, res.text().await.unwrap()); ++ log::error!( ++ "thread {} case5 get token failed status: {:?} response: {:?}", ++ i, ++ status, ++ res.text().await.unwrap() ++ ); + return; + } + }; +@@ -165,15 +201,25 @@ async fn aa_proc(i: i64) { + match res.status() { + reqwest::StatusCode::OK => { + let respone = res.text().await.unwrap(); +- log::info!("thread {} case6 verify token success response: {:?}", i, respone); ++ log::info!( ++ "thread {} case6 verify token success response: {:?}", ++ i, ++ respone ++ ); + } + status => { +- log::error!("thread {} case6 verify token failed response: {:?}", i, status); +- log::error!("thread case6 verify token failed response:{}", res.text().await.unwrap()); ++ log::error!( ++ "thread {} case6 verify token failed response: {:?}", ++ i, ++ status ++ ); ++ log::error!( ++ "thread case6 verify token failed response:{}", ++ res.text().await.unwrap() ++ ); + } + } + } +- + + log::info!("attestation_proc thread {} end", i); +-} +\ No newline at end of file ++} +diff --git a/service/attestation/attestation-agent/agent/src/lib.rs b/service/attestation/attestation-agent/agent/src/lib.rs +index f1c4510..d1234d1 100644 +--- a/service/attestation/attestation-agent/agent/src/lib.rs ++++ b/service/attestation/attestation-agent/agent/src/lib.rs +@@ -11,24 +11,30 @@ + */ + + //! 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}; ++pub mod restapi; ++pub mod result; ++ ++use actix_web::web::Bytes; ++use anyhow::{anyhow, bail, Context, Result}; + use async_trait::async_trait; ++use attestation_types::{resource::ResourceLocation, service::GetResourceOp}; ++use attester::{Attester, AttesterAPIs}; ++use log; ++use rand::RngCore; ++use reqwest::Client; ++use result::Error; ++use serde::{Deserialize, Serialize}; ++use serde_json::json; ++use serde_json::Value; + use std::fs::File; + use std::path::Path; +-use rand::RngCore; + use thiserror; ++use token_verifier::{TokenRawData, TokenVerifier, TokenVerifyConfig}; + +-use attester::{Attester, AttesterAPIs}; +-use token_verifier::{TokenVerifyConfig, TokenVerifier, TokenRawData}; +- +-pub mod result; +-use result::Error; + pub type TeeClaim = serde_json::Value; + + #[derive(Debug, thiserror::Error)] +@@ -52,15 +58,14 @@ use verifier::{Verifier, VerifierAPIs}; + + #[cfg(not(feature = "no_as"))] + use { +- serde_json::json, ++ base64_url, + reqwest::header::{HeaderMap, HeaderValue}, +- base64_url + }; + + pub use attester::EvidenceRequest; + mod session; +-use session::{SessionMap, Session}; + use attestation_types::SESSION_TIMEOUT_MIN; ++use session::{Session, SessionMap}; + + pub type AsTokenClaim = TokenRawData; + +@@ -72,7 +77,7 @@ pub struct TokenRequest { + + #[async_trait] + pub trait AttestationAgentAPIs { +- async fn get_challenge(&self) -> Result; ++ async fn get_challenge(&self, user_data: Option>) -> Result; + + /// `get_evidence`: get hardware TEE signed evidence due to given user_data, + /// such as input random challenge to prevent replay attacks +@@ -80,65 +85,82 @@ pub trait AttestationAgentAPIs { + + /// `verify_evidence`: verify the integrity of TEE evidence and evaluate the + /// claims against the supplied reference values +- async fn verify_evidence(&self, ++ async fn verify_evidence( ++ &self, + challenge: &[u8], + evidence: &[u8], +- policy_id: Option> ++ 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 fn get_resource( ++ &self, ++ challenge: &str, ++ restful: &str, ++ resource: ResourceLocation, ++ token: &str, ++ ) -> Result; + } + + #[async_trait] + impl AttestationAgentAPIs for AttestationAgent { + // no_as generate by agent; has as generate by as +- async fn get_challenge(&self) -> Result { ++ async fn get_challenge(&self, user_data: Option>) -> Result { + #[cfg(feature = "no_as")] +- return self.generate_challenge_local().await; ++ return self.generate_challenge_local(user_data).await; + + #[cfg(not(feature = "no_as"))] +- return self.get_challenge_from_as().await; ++ return self.get_challenge_from_as(user_data).await; + } + async fn get_evidence(&self, user_data: EvidenceRequest) -> Result> { + Attester::default().tee_get_evidence(user_data).await + } +- async fn verify_evidence(&self, ++ async fn verify_evidence( ++ &self, + challenge: &[u8], + evidence: &[u8], +- _policy_id: Option> ++ _policy_id: Option>, + ) -> Result { + #[cfg(feature = "no_as")] + { +- let ret = Verifier::default().verify_evidence(challenge, evidence).await; ++ 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); ++ 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; ++ let ret = self ++ .verify_evidence_by_as(challenge, evidence, _policy_id) ++ .await; + match ret { +- Ok(token) => { self.token_to_teeclaim(token).await }, ++ 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()); ++ return Ok("no as in not support get token".to_string()); + } + // todo token 有效期内,不再重新获取报告 + #[cfg(not(feature = "no_as"))] +@@ -147,7 +169,9 @@ impl AttestationAgentAPIs for AttestationAgent { + 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; ++ return self ++ .verify_evidence_by_as(challenge, &evidence, policy_id) ++ .await; + } + } + +@@ -156,11 +180,63 @@ impl AttestationAgentAPIs for AttestationAgent { + let result = verifier.verify(&token)?; + Ok(result) + } ++ ++ async fn get_resource( ++ &self, ++ challenge: &str, ++ restful: &str, ++ resource: ResourceLocation, ++ token: &str, ++ ) -> Result { ++ #[cfg(feature = "no_as")] ++ { ++ bail!("resource can only be gotten from attestation server!") ++ } ++ let rest = self ++ .get_resource_from_as(challenge, restful, resource, token) ++ .await?; ++ Ok(String::from_utf8(rest.to_vec())?) ++ } + } + + #[derive(Clone, Debug, Serialize, Deserialize)] +-struct AAConfig { +- svr_url: String, // Attestation Service url ++pub enum HttpProtocal { ++ Http { protocal: String }, ++ // If https is uesd, the root certificate must be provided. ++ Https { protocal: String, cert_root: String }, ++} ++ ++impl Default for HttpProtocal { ++ fn default() -> Self { ++ Self::Http { ++ protocal: "http".to_string(), ++ } ++ } ++} ++ ++impl HttpProtocal { ++ pub fn get_protocal(&self) -> String { ++ match self { ++ Self::Http { protocal } => protocal, ++ Self::Https { protocal, .. } => protocal, ++ } ++ .clone() ++ } ++ ++ pub fn get_cert_root(&self) -> Option { ++ match self { ++ Self::Https { cert_root, .. } => Some(cert_root.clone()), ++ _ => None, ++ } ++ } ++} ++ ++#[derive(Clone, Debug, Serialize, Deserialize)] ++pub struct AAConfig { ++ // Attestation Service url ++ pub svr_url: String, ++ // Http protocal, such as http or https ++ pub protocal: HttpProtocal, + token_cfg: TokenVerifyConfig, + } + +@@ -169,6 +245,7 @@ impl Default for AAConfig { + Self { + svr_url: String::from("http://127.0.0.1:8080"), + token_cfg: TokenVerifyConfig::default(), ++ protocal: HttpProtocal::default(), + } + } + } +@@ -191,23 +268,13 @@ impl TryFrom<&Path> for AAConfig { + + #[derive(Debug)] + pub struct AttestationAgent { +- config: AAConfig, ++ pub config: AAConfig, + as_client_sessions: SessionMap, + } + + #[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() +- } +- }; ++ pub fn new(config: AAConfig) -> Result { + let as_client_sessions = SessionMap::new(); + let sessions = as_client_sessions.clone(); + tokio::spawn(async move { +@@ -225,13 +292,52 @@ impl AttestationAgent { + }) + } + ++ fn create_client(&self, protocal: HttpProtocal, cookie_store: bool) -> Result { ++ let client: Client = match protocal { ++ HttpProtocal::Http { protocal: _ } => reqwest::Client::builder() ++ .cookie_store(cookie_store) ++ .build()?, ++ HttpProtocal::Https { ++ protocal: _, ++ cert_root, ++ } => { ++ let cert = reqwest::Certificate::from_pem(cert_root.as_bytes())?; ++ reqwest::Client::builder() ++ .cookie_store(cookie_store) ++ .add_root_certificate(cert) ++ .build()? ++ } ++ }; ++ ++ Ok(client) ++ } ++ + #[cfg(not(feature = "no_as"))] +- async fn verify_evidence_by_as(&self, ++ async fn verify_evidence_by_as( ++ &self, + challenge: &[u8], + evidence: &[u8], +- policy_id: Option> ++ policy_id: Option>, + ) -> Result { +- let challenge = base64_url::encode(challenge); ++ let challenge = String::from_utf8_lossy(challenge).to_string(); ++ let mut session = match self ++ .as_client_sessions ++ .session_map ++ .get_async(&challenge) ++ .await ++ { ++ Some(entry) => entry, ++ None => { ++ // Challenge should be posted to service previously. ++ bail!("challenge '{}' does not exist in sessions", challenge); ++ } ++ }; ++ ++ // If the session is already attested, directly use the token. ++ if let Some(t) = session.get().token.as_ref() { ++ return Ok(t.clone()); ++ } ++ + let request_body = json!({ + "challenge": challenge, + "evidence": base64_url::encode(evidence), +@@ -239,21 +345,8 @@ impl AttestationAgent { + }); + let mut map = HeaderMap::new(); + map.insert("Content-Type", HeaderValue::from_static("application/json")); +- let mut client = reqwest::Client::new(); +- if !self.as_client_sessions.session_map.is_empty() { +- let session = self.as_client_sessions +- .session_map +- .get_async(&challenge) +- .await; +- match session { +- Some(entry) => { +- map.insert("as-challenge", HeaderValue::from_static("as")); +- client = entry.get().as_client.clone() +- }, +- None => log::info!("challenge is not as generate"), +- } +- } +- ++ map.insert("as-challenge", HeaderValue::from_static("as")); ++ let client = session.get().as_client.clone(); + let attest_endpoint = format!("{}/attestation", self.config.svr_url); + let res = client + .post(attest_endpoint) +@@ -265,11 +358,15 @@ impl AttestationAgent { + match res.status() { + reqwest::StatusCode::OK => { + let token = res.text().await?; ++ session.get_mut().token = Some(token.clone()); + log::debug!("Remote Attestation success, AS Response: {:?}", token); + Ok(token) + } + _ => { +- bail!("Remote Attestation Failed, AS Response: {:?}", res.text().await?); ++ bail!( ++ "Remote Attestation Failed, AS Response: {:?}", ++ res.text().await? ++ ); + } + } + } +@@ -279,36 +376,42 @@ impl AttestationAgent { + 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) +- }, ++ let token_claim: serde_json::Value = ++ serde_json::from_slice(token.claim.as_bytes())?; ++ Ok(token_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]; ++ async fn generate_challenge_local(&self, user_data: Option>) -> Result { ++ let mut nonce: Vec = vec![0; 32]; + rand::thread_rng().fill_bytes(&mut nonce); ++ if user_data != None { ++ nonce.append(&mut user_data.unwrap()); ++ } + Ok(base64_url::encode(&nonce)) + } +- async fn get_challenge_from_as(&self) -> Result { ++ ++ async fn get_challenge_from_as(&self, user_data: Option>) -> Result { + let challenge_endpoint = format!("{}/challenge", self.config.svr_url); +- let client = reqwest::Client::builder() +- .cookie_store(true) +- .build()?; ++ let client = self.create_client(self.config.protocal.clone(), true)?; ++ let data: Value; ++ if user_data.is_some() { ++ data = json!({"user_data":user_data.unwrap()}); ++ } else { ++ data = Value::Null; ++ } + let res = client + .get(challenge_endpoint) + .header("Content-Type", "application/json") +- .header("content-length", 0) ++ .body(data.to_string()) + .send() + .await?; ++ + let challenge = match res.status() { + reqwest::StatusCode::OK => { + let respone: String = res.json().await.unwrap(); +@@ -324,8 +427,50 @@ impl AttestationAgent { + self.as_client_sessions.insert(session); + Ok(challenge) + } +-} + ++ async fn get_resource_from_as( ++ &self, ++ challenge: &str, ++ restful: &str, ++ resource: ResourceLocation, ++ token: &str, ++ ) -> Result { ++ // Use the client in the attested session to ++ let session = match self ++ .as_client_sessions ++ .session_map ++ .get_async(challenge) ++ .await ++ { ++ Some(s) => s, ++ None => bail!("getting resource failed because the session is missing"), ++ }; ++ ++ let payload = GetResourceOp::TeeGet { resource }; ++ ++ let response = session ++ .get() ++ .as_client ++ .get(restful) ++ .bearer_auth(token) ++ .json(&payload) ++ .send() ++ .await?; ++ let resource = match response.status() { ++ reqwest::StatusCode::OK => { ++ let respone = response.bytes().await.unwrap(); ++ log::debug!("get resource success, AS Response: {:?}", respone); ++ respone ++ } ++ status => { ++ log::error!("get resource Failed, AS Response: {:?}", status); ++ bail!("get resource Failed") ++ } ++ }; ++ ++ Ok(resource) ++ } ++} + + // attestation agent c interface + use safer_ffi::prelude::*; +@@ -341,14 +486,20 @@ pub fn init_env_logger(c_level: Option<&repr_c::String>) { + } + + #[ffi_export] +-pub fn get_report(c_challenge: Option<&repr_c::Vec>, c_ima: &repr_c::TaggedOption) -> repr_c::Vec { ++pub fn get_report( ++ c_challenge: Option<&repr_c::Vec>, ++ c_ima: &repr_c::TaggedOption, ++) -> repr_c::Vec { + 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();}, ++ None => { ++ log::error!("challenge is null"); ++ return Vec::new().into(); ++ } + Some(cha) => cha.clone().to_vec(), + }; + +@@ -358,8 +509,12 @@ pub fn get_report(c_challenge: Option<&repr_c::Vec>, c_ima: &repr_c::TaggedO + ima: Some(ima), + }; + let rt = Runtime::new().unwrap(); ++ let config = AAConfig::try_from(Path::new(DEFAULT_AACONFIG_FILE)).unwrap(); + let fut = async { +- AttestationAgent::new(Some(DEFAULT_AACONFIG_FILE.to_string())).unwrap().get_evidence(input).await ++ AttestationAgent::new(config) ++ .unwrap() ++ .get_evidence(input) ++ .await + }; + let ret = rt.block_on(fut); + let report: Vec = match ret { +@@ -367,44 +522,83 @@ pub fn get_report(c_challenge: Option<&repr_c::Vec>, c_ima: &repr_c::TaggedO + Err(e) => { + log::error!("get report failed {:?}", e); + Vec::new() +- }, ++ } + }; + + report.into() + } + ++#[cfg(feature = "no_as")] ++use verifier::virtcca_parse_evidence; ++ ++#[cfg(feature = "no_as")] + #[ffi_export] +-pub fn verify_report(c_challenge: Option<&repr_c::Vec>, report: Option<&repr_c::Vec>) -> repr_c::String { ++pub fn parse_report(report: Option<&repr_c::Vec>) -> repr_c::String { ++ 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 { virtcca_parse_evidence(&report) }; ++ 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 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 config = AAConfig::try_from(Path::new(DEFAULT_AACONFIG_FILE)).unwrap(); ++ let fut = async { ++ AttestationAgent::new(config) ++ .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) =>{ ++ } ++ Err(e) => { + log::error!("{e}"); + "".to_string() +- }, ++ } + }; +- ++ + return ret.into(); + } + +diff --git a/service/attestation/attestation-agent/agent/src/main.rs b/service/attestation/attestation-agent/agent/src/main.rs +index 62a4b4d..7cabf79 100644 +--- a/service/attestation/attestation-agent/agent/src/main.rs ++++ b/service/attestation/attestation-agent/agent/src/main.rs +@@ -9,26 +9,30 @@ + * 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 actix_web::{web, App, HttpResponse, HttpServer}; ++use anyhow::{bail, Result}; ++use attestation_agent::{ ++ restapi::{ ++ get_challenge, get_evidence, get_resource, get_token, verify_evidence, verify_token, ++ }, ++ AAConfig, AttestationAgent, HttpProtocal, DEFAULT_AACONFIG_FILE, ++}; ++use clap::{arg, command, Parser}; + use env_logger; +-use actix_web::{web, App, HttpServer, HttpResponse}; +-use std::{net::{SocketAddr, IpAddr, Ipv4Addr}, sync::Arc}; ++use std::{path::Path, 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); ++const DEFAULT_SOCKETADDR: &str = "127.0.0.1:8081"; + + #[derive(Parser, Debug)] + #[command(version, about, long_about = None)] +-struct Cli { ++pub struct Cli { + /// Socket address to listen on +- #[arg(short, long, default_value_t = DEFAULT_SOCKETADDR)] +- socketaddr: SocketAddr, +- ++ #[arg(short, long, default_value_t = DEFAULT_SOCKETADDR.to_string())] ++ socketaddr: String, ++ /// Socket address connect to ++ #[arg(short = 'u', long, default_value_t = String::from(""))] ++ serverurl: String, + /// Load `AAConfig` from a configuration file like: + /// { + /// "svr_url": "http://127.0.0.1:8080", +@@ -39,6 +43,13 @@ struct Cli { + /// } + #[arg(short, long, default_value_t = DEFAULT_AACONFIG_FILE.to_string())] + config: String, ++ ++ #[arg(short = 'p', long = "protocol", default_value_t = String::from("http"))] ++ protocol: String, ++ ++ /// root certificate to verify peer ++ #[arg(short = 't', long = "cert_root", default_value_t = String::from(""))] ++ cert_root: String, + } + + #[actix_web::main] +@@ -46,8 +57,29 @@ async fn main() -> Result<()> { + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); + + let cli = Cli::parse(); +- let server = AttestationAgent::new(Some(cli.config)).unwrap(); ++ // Load config content from file. ++ let mut config = AAConfig::try_from(Path::new(&cli.config))?; + ++ // Override configurations if set by command line tool. ++ match cli.protocol.as_ref() { ++ "http" => {} ++ "https" => { ++ config.protocal = HttpProtocal::Https { ++ protocal: "https".to_string(), ++ cert_root: std::fs::read_to_string(cli.cert_root)?, ++ } ++ } ++ _ => { ++ bail!("Invalid http protocal!"); ++ } ++ } ++ ++ // Override the listening url. ++ if cli.serverurl != "" { ++ config.svr_url = config.protocal.get_protocal() + "://" + &cli.serverurl.clone(); ++ } ++ ++ let server = AttestationAgent::new(config).unwrap(); + let service = web::Data::new(Arc::new(RwLock::new(server))); + HttpServer::new(move || { + App::new() +@@ -57,11 +89,12 @@ async fn main() -> Result<()> { + .service(verify_evidence) + .service(get_token) + .service(verify_token) ++ .service(get_resource) + .default_service(web::to(|| HttpResponse::NotFound())) + }) +- .bind((cli.socketaddr.ip().to_string(), cli.socketaddr.port()))? ++ .bind(cli.socketaddr)? + .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 +index 2745443..3cb3a8c 100644 +--- a/service/attestation/attestation-agent/agent/src/restapi/mod.rs ++++ b/service/attestation/attestation-agent/agent/src/restapi/mod.rs +@@ -9,27 +9,52 @@ + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ +-use attestation_agent::{AttestationAgent, AttestationAgentAPIs, TokenRequest, AgentError}; +-use attestation_agent::result::Result; +- +-use actix_web::{ post, get, web, HttpResponse}; ++use crate::result::Result; ++use crate::{AgentError, AttestationAgent, AttestationAgentAPIs, TokenRequest}; ++use actix_web::{get, post, web, HttpResponse}; ++use attestation_types::resource::ResourceLocation; + use attester::EvidenceRequest; ++use log; + use serde::{Deserialize, Serialize}; + use std::sync::Arc; + use tokio::sync::RwLock; +-use log; ++ ++#[cfg(feature = "no_as")] ++use crate::result::Error; ++#[cfg(feature = "no_as")] ++use anyhow::anyhow; + + #[derive(Deserialize, Serialize, Debug)] +-struct GetChallengeRequest {} ++struct GetChallengeRequest { ++ pub user_data: Vec, ++} + + #[get("/challenge")] + pub async fn get_challenge( +- //_request: web::Json, ++ request: Option>, + agent: web::Data>>, + ) -> Result { +- //let request = request.0; + log::debug!("get challenge request"); +- let challenge = agent.read().await.get_challenge().await ++ let user_data: Option>; ++ if request.is_some() { ++ user_data = Some(request.unwrap().0.user_data); ++ if user_data.clone().unwrap().len() > 32 { ++ return Err(crate::result::Error::Agent { ++ source: AgentError::ChallengeError(String::from( ++ "user data length should not exceed 32", ++ )), ++ }); ++ } ++ log::debug!("user data is {:?}", user_data.clone().unwrap()); ++ } else { ++ log::debug!("user data is None"); ++ user_data = Option::None; ++ } ++ let challenge = agent ++ .read() ++ .await ++ .get_challenge(user_data) ++ .await + .map_err(|err| AgentError::ChallengeError(err.to_string()))?; + + Ok(HttpResponse::Ok().body(challenge)) +@@ -49,18 +74,20 @@ pub async fn get_evidence( + ) -> Result { + let request = request.0; + log::debug!("get evidence request: {:?}", request); +- let challenge = base64_url::decode(&request.challenge) +- .map_err(|err|AgentError::DecodeError(err.to_string()))?; ++ let challenge = request.challenge; + let uuid = request.uuid; +- let ima = request.ima; ++ let ima = request.ima; + let input = EvidenceRequest { + uuid: uuid, +- challenge: challenge, ++ challenge: challenge.into_bytes(), + ima: ima, + }; +- let evidence = agent.read().await.get_evidence(input).await +- .map_err(|err|AgentError::GetEvidenceError(err.to_string()))?; +- ++ let evidence = agent ++ .read() ++ .await ++ .get_evidence(input) ++ .await ++ .map_err(|err| AgentError::GetEvidenceError(err.to_string()))?; + + Ok(HttpResponse::Ok().body(evidence)) + } +@@ -78,13 +105,16 @@ pub async fn verify_evidence( + ) -> Result { + let request = request.0; + log::debug!("verify evidence request: {:?}", request); +- let challenge = base64_url::decode(&request.challenge) +- .map_err(|err|AgentError::DecodeError(err.to_string()))?; ++ let challenge = request.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 +- .map_err(|err|AgentError::VerifyEvidenceError(err.to_string()))?; ++ let policy_id = request.policy_id; ++ ++ let claim = agent ++ .read() ++ .await ++ .verify_evidence(&challenge.into_bytes(), evidence.as_bytes(), policy_id) ++ .await ++ .map_err(|err| AgentError::VerifyEvidenceError(err.to_string()))?; + let string_claim = serde_json::to_string(&claim)?; + + Ok(HttpResponse::Ok().body(string_claim)) +@@ -105,14 +135,13 @@ pub async fn get_token( + ) -> Result { + let request = request.0; + log::debug!("get token request: {:?}", request); +- let challenge = base64_url::decode(&request.challenge) +- .map_err(|err|AgentError::DecodeError(err.to_string()))?; ++ let challenge = request.challenge; + let uuid = request.uuid; + let ima = request.ima; +- let policy_id = request.policy_id; ++ let policy_id = request.policy_id; + let ev = EvidenceRequest { + uuid: uuid, +- challenge: challenge, ++ challenge: challenge.into_bytes(), + ima: ima, + }; + let input = TokenRequest { +@@ -120,8 +149,12 @@ pub async fn get_token( + policy_id: policy_id, + }; + +- let token = agent.read().await.get_token(input).await +- .map_err(|err|AgentError::GetTokenError(err.to_string()))?; ++ let token = agent ++ .read() ++ .await ++ .get_token(input) ++ .await ++ .map_err(|err| AgentError::GetTokenError(err.to_string()))?; + + Ok(HttpResponse::Ok().body(token)) + } +@@ -138,10 +171,70 @@ pub async fn verify_token( + let request = request.0; + log::debug!("verify token request: {:?}", request); + +- let claim = agent.read().await.verify_token(request.token).await +- .map_err(|err|AgentError::VerifyTokenError(err.to_string()))?; ++ let claim = agent ++ .read() ++ .await ++ .verify_token(request.token) ++ .await ++ .map_err(|err| AgentError::VerifyTokenError(err.to_string()))?; + let string_claim = serde_json::to_string(&claim) +- .map_err(|err|AgentError::VerifyTokenError(err.to_string()))?; ++ .map_err(|err| AgentError::VerifyTokenError(err.to_string()))?; + + Ok(HttpResponse::Ok().body(string_claim)) +-} +\ No newline at end of file ++} ++ ++#[derive(Deserialize, Serialize, Debug)] ++struct GetResourceRequest { ++ uuid: String, ++ challenge: Option, ++ ima: Option, ++ policy_id: Option>, ++ resource: ResourceLocation, ++} ++ ++#[get("/resource/storage")] ++pub async fn get_resource( ++ request: web::Json, ++ agent: web::Data>>, ++) -> Result { ++ let agent = agent.read().await; ++ ++ // If user provides the challenge number, use the challenge to find session. ++ let challenge = match request.challenge.as_ref() { ++ Some(c) => c.clone(), ++ None => agent ++ .get_challenge(None) ++ .await ++ .map_err(|err| AgentError::ChallengeError(err.to_string()))?, ++ }; ++ ++ // base64 encoded challenge ++ let ev_req = EvidenceRequest { ++ uuid: request.uuid.clone(), ++ challenge: challenge.clone().into_bytes(), ++ ima: request.ima, ++ }; ++ ++ let token_req = TokenRequest { ++ ev_req, ++ policy_id: request.policy_id.clone(), ++ }; ++ ++ #[cfg(feature = "no_as")] ++ { ++ return Err(Error::Other(anyhow!( ++ "Resource can only be got from attestation server." ++ ))); ++ } ++ ++ let token = agent.get_token(token_req).await?; ++ ++ let restful = format!("{}/resource/storage", agent.config.svr_url,); ++ ++ let resource = agent ++ .get_resource(&challenge, &restful, request.resource.clone(), &token) ++ .await ++ .map_err(|err| AgentError::GetTokenError(err.to_string()))?; ++ ++ Ok(HttpResponse::Ok().body(resource)) ++} +diff --git a/service/attestation/attestation-agent/agent/src/result/mod.rs b/service/attestation/attestation-agent/agent/src/result/mod.rs +index a33be0c..b5dd02b 100644 +--- a/service/attestation/attestation-agent/agent/src/result/mod.rs ++++ b/service/attestation/attestation-agent/agent/src/result/mod.rs +@@ -47,6 +47,9 @@ pub enum Error { + #[error("Attestation Agent error:{0}")] + AttestationAgentError(String), + ++ #[error("Client is missing, challenge is invalid.")] ++ ClientMissing, ++ + #[error(transparent)] + Other(#[from] anyhow::Error), + } +diff --git a/service/attestation/attestation-agent/agent/src/session.rs b/service/attestation/attestation-agent/agent/src/session.rs +index 5e1c1fc..d4896df 100644 +--- a/service/attestation/attestation-agent/agent/src/session.rs ++++ b/service/attestation/attestation-agent/agent/src/session.rs +@@ -9,28 +9,27 @@ + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ +-use actix_web::cookie::{time::{Duration, OffsetDateTime}}; +-use scc::HashMap; ++use actix_web::cookie::time::{Duration, OffsetDateTime}; + use anyhow::Result; ++use scc::HashMap; + + #[derive(Debug, Clone)] + pub struct Session { + pub challenge: String, + pub as_client: reqwest::Client, + timeout: OffsetDateTime, +- // pub token: Option, ++ /// If token is not none, this session is already attested by attestation server. Then directly use the token. ++ pub token: Option, + } + + impl Session { + pub fn new(challenge: String, as_client: reqwest::Client, timeout_m: i64) -> Result { +- + let timeout = OffsetDateTime::now_utc() + Duration::minutes(timeout_m); +- // let token = None; + Ok(Session { + challenge, + as_client, + timeout, +- // token, ++ token: None, + }) + } + pub fn is_expired(&self) -> bool { +@@ -52,4 +51,4 @@ impl SessionMap { + pub fn insert(&self, session: Session) { + let _ = self.session_map.insert(session.challenge.clone(), session); + } +-} +\ No newline at end of file ++} +diff --git a/service/attestation/attestation-agent/attester/Cargo.toml b/service/attestation/attestation-agent/attester/Cargo.toml +index a7dae2a..2c6a012 100644 +--- a/service/attestation/attestation-agent/attester/Cargo.toml ++++ b/service/attestation/attestation-agent/attester/Cargo.toml +@@ -4,8 +4,8 @@ version = "0.1.0" + edition = "2021" + + [features] +-itrustee-attester = [ "base64-url", "rand" ] +-virtcca-attester = [] ++itrustee-attester = ["base64-url", "rand"] ++virtcca-attester = ["base64-url"] + + [dependencies] + anyhow.workspace = true +diff --git a/service/attestation/attestation-agent/attester/src/itrustee/mod.rs b/service/attestation/attestation-agent/attester/src/itrustee/mod.rs +index 3fde5f7..22b6afd 100644 +--- a/service/attestation/attestation-agent/attester/src/itrustee/mod.rs ++++ b/service/attestation/attestation-agent/attester/src/itrustee/mod.rs +@@ -11,15 +11,15 @@ + */ + + //! 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 serde::{Deserialize, Serialize}; ++use serde_json; ++use std::path::Path; + + use crate::EvidenceRequest; + +@@ -59,10 +59,24 @@ struct ItrusteeInput { + handler: String, + payload: ReportInputPayload, + } +- ++const MAX_CHALLENGE_LEN: usize = 64; + fn itrustee_get_evidence(user_data: EvidenceRequest) -> Result { ++ let challenge = base64_url::decode(&user_data.challenge)?; ++ let len = 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 ++ ); ++ } + let payload = ReportInputPayload { +- nonce: base64_url::encode(&user_data.challenge), ++ nonce: String::from_utf8(user_data.challenge)?, + uuid: user_data.uuid, + with_tcb: false, + request_key: true, +diff --git a/service/attestation/attestation-agent/attester/src/lib.rs b/service/attestation/attestation-agent/attester/src/lib.rs +index 3c02946..6dd549d 100644 +--- a/service/attestation/attestation-agent/attester/src/lib.rs ++++ b/service/attestation/attestation-agent/attester/src/lib.rs +@@ -11,13 +11,12 @@ + */ + + //! 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; +@@ -42,22 +41,17 @@ pub trait AttesterAPIs { + #[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, ++ let evidence = itrustee::ItrusteeAttester::default() ++ .tee_get_evidence(_user_data) ++ .await?; ++ let aa_evidence = attestation_types::Evidence { ++ tee: attestation_types::TeeType::Itrustee, + evidence: evidence, + }; + let evidence = serde_json::to_vec(&aa_evidence)?; +@@ -66,14 +60,16 @@ impl AttesterAPIs for Attester { + } + #[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, ++ let evidence = virtcca::VirtccaAttester::default() ++ .tee_get_evidence(_user_data) ++ .await?; ++ let aa_evidence = attestation_types::Evidence { ++ tee: attestation_types::TeeType::Virtcca, + evidence: evidence, + }; + let evidence = serde_json::to_vec(&aa_evidence)?; + return Ok(evidence); + } +- bail!("unkown tee platform"); ++ bail!("unknown 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 +index c981d91..86f0061 100644 +--- a/service/attestation/attestation-agent/attester/src/virtcca/mod.rs ++++ b/service/attestation/attestation-agent/attester/src/virtcca/mod.rs +@@ -11,24 +11,23 @@ + */ + + //! 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 anyhow::{bail, Result}; + use attestation_types::VirtccaEvidence; ++use log; ++use std::path::Path; + +-use crate::EvidenceRequest; ++use self::virtcca::{get_attestation_token, get_dev_cert, tsi_new_ctx}; + use crate::virtcca::virtcca::tsi_free_ctx; +-use self::virtcca::{tsi_new_ctx, get_attestation_token, get_dev_cert}; ++use crate::EvidenceRequest; + + 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)?; +@@ -41,12 +40,24 @@ pub fn detect_platform() -> bool { + Path::new("/dev/tsi").exists() + } + +- ++const MAX_CHALLENGE_LEN: usize = 64; + fn virtcca_get_token(user_data: EvidenceRequest) -> Result { ++ let mut challenge = base64_url::decode(&user_data.challenge)?; ++ let len = 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 ++ ); ++ } + 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(); +@@ -78,7 +89,9 @@ fn virtcca_get_token(user_data: EvidenceRequest) -> Result { + None => false, + }; + let ima_log = match with_ima { +- true => Some(std::fs::read("/sys/kernel/security/ima/binary_runtime_measurements").unwrap()), ++ true => { ++ Some(std::fs::read("/sys/kernel/security/ima/binary_runtime_measurements").unwrap()) ++ } + false => None, + }; + +@@ -90,4 +103,4 @@ fn virtcca_get_token(user_data: EvidenceRequest) -> Result { + 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 +index 33318c7..82fd6a0 100644 +--- a/service/attestation/attestation-agent/attester/src/virtcca/virtcca.rs ++++ b/service/attestation/attestation-agent/attester/src/virtcca/virtcca.rs +@@ -85,11 +85,7 @@ extern "C" { + } + extern "C" { + #[allow(dead_code)] +- pub fn get_version( +- ctx: *mut tsi_ctx, +- major: *mut wchar_t, +- minor: *mut wchar_t, +- ) -> wchar_t; ++ 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( +diff --git a/service/attestation/attestation-agent/c_header/example.c b/service/attestation/attestation-agent/c_header/example.c +new file mode 100644 +index 0000000..a75d018 +--- /dev/null ++++ b/service/attestation/attestation-agent/c_header/example.c +@@ -0,0 +1,90 @@ ++/* ++ * 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. ++ */ ++ ++// gcc example.c -o aa-test -L. -lattestation_agent -lcrypto ++#include "rust_attestation_agent.h" ++ ++#include ++#include ++#include ++#include ++#include ++ ++#define CHALLENGE_LEN 32 ++#define TEST_THREAD_NUM 1 ++ ++void *thread_proc(void *arg) ++{ ++ // step1: generate random numbers ++ uint8_t nonce[CHALLENGE_LEN]; ++ RAND_priv_bytes(nonce, CHALLENGE_LEN); ++ Vec_uint8_t challenge = { ++ .ptr = (uint8_t *)&nonce, ++ .len = CHALLENGE_LEN, ++ .cap = CHALLENGE_LEN, ++ }; ++ ++ // step2: define ima input param ++ Tuple2_bool_bool_t ima = { // define input ima = Some(false) ++ ._0 = true, ++ ._1 = false, // true: enable to get report with ima ++ }; ++ ++ // step3: get report ++ Vec_uint8_t report = get_report(&challenge, &ima); ++ Vec_uint8_t claim; ++ if (report.len != 0) { ++ report.ptr[report.len] = '\0'; // rust return string has no '\0' ++ printf("get report success, report:%s\n", report.ptr); ++ ++ // parse report ++ // Vec_uint8_t claim_no_verify = parse_report(&report); ++ // if (claim_no_verify.len != 0) { ++ // claim_no_verify.ptr[claim_no_verify.len] = '\0'; ++ // printf("parse report success: %s\n", claim_no_verify.ptr); ++ // } ++ // free_rust_vec(claim_no_verify); ++ ++ // step4: verify report ++ claim = verify_report(&challenge, &report); ++ } ++ ++ if (claim.len != 0) { ++ claim.ptr[claim.len] = '\0'; // rust return string has no '\0' ++ printf("verify report, return claim:%s\n", claim.ptr); ++ } ++ ++ // step5: free rust resource ++ free_rust_vec(report); ++ free_rust_vec(claim); ++} ++int main() ++{ ++ char *level = "info"; ++ Vec_uint8_t log_level = { ++ .ptr = (uint8_t *)level, ++ .len = strlen(level), ++ .cap = strlen(level), ++ }; ++ init_env_logger(&log_level); ++ ++ pthread_t tids[TEST_THREAD_NUM]; ++ for (int i = 0; i < TEST_THREAD_NUM; i++) { ++ pthread_create(&tids[i], NULL, thread_proc, NULL); ++ } ++ ++ for (int i = 0; i < TEST_THREAD_NUM; i++) { ++ pthread_join(tids[i], NULL); ++ } ++ ++ return 0; ++} +diff --git a/service/attestation/attestation-agent/c_header/rust_attestation_agent.h b/service/attestation/attestation-agent/c_header/rust_attestation_agent.h +new file mode 100644 +index 0000000..9c1a18f +--- /dev/null ++++ b/service/attestation/attestation-agent/c_header/rust_attestation_agent.h +@@ -0,0 +1,81 @@ ++/*! \file */ ++/******************************************* ++ * * ++ * File auto-generated by `::safer_ffi`. * ++ * * ++ * Do not manually edit this file. * ++ * * ++ *******************************************/ ++ ++#ifndef __RUST_ATTESTATION_AGENT__ ++#define __RUST_ATTESTATION_AGENT__ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++ ++#include ++#include ++ ++/** \brief ++ * Same as [`Vec`][`rust::Vec`], but with guaranteed `#[repr(C)]` layout ++ */ ++typedef struct Vec_uint8 { ++ /** */ ++ uint8_t * ptr; ++ ++ /** */ ++ size_t len; ++ ++ /** */ ++ size_t cap; ++} Vec_uint8_t; ++ ++/** */ ++void ++free_rust_vec ( ++ Vec_uint8_t vec); ++ ++ ++#include ++ ++/** \brief ++ * Simplified for lighter documentation, but the actual impls ++ * range from `Tuple1` up to `Tuple6`. ++ */ ++typedef struct Tuple2_bool_bool { ++ /** */ ++ bool _0; ++ ++ /** */ ++ bool _1; ++} Tuple2_bool_bool_t; ++ ++/** */ ++Vec_uint8_t ++get_report ( ++ Vec_uint8_t const * c_challenge, ++ Tuple2_bool_bool_t const * c_ima); ++ ++/** */ ++void ++init_env_logger ( ++ Vec_uint8_t const * c_level); ++ ++/** */ ++Vec_uint8_t ++parse_report ( ++ Vec_uint8_t const * report); ++ ++/** */ ++Vec_uint8_t ++verify_report ( ++ Vec_uint8_t const * c_challenge, ++ Vec_uint8_t const * report); ++ ++ ++#ifdef __cplusplus ++} /* extern \"C\" */ ++#endif ++ ++#endif /* __RUST_ATTESTATION_AGENT__ */ +diff --git a/service/attestation/attestation-agent/token/Cargo.toml b/service/attestation/attestation-agent/token/Cargo.toml +index 916f2a2..d4e8c0d 100644 +--- a/service/attestation/attestation-agent/token/Cargo.toml ++++ b/service/attestation/attestation-agent/token/Cargo.toml +@@ -11,4 +11,4 @@ serde.workspace = true + serde_json.workspace = true + anyhow.workspace = true + attestation-types.workspace = true +-thiserror.workspace = true +\ No newline at end of file ++thiserror.workspace = true +diff --git a/service/attestation/attestation-agent/token/src/lib.rs b/service/attestation/attestation-agent/token/src/lib.rs +index 37aab9e..aea0293 100644 +--- a/service/attestation/attestation-agent/token/src/lib.rs ++++ b/service/attestation/attestation-agent/token/src/lib.rs +@@ -9,10 +9,10 @@ + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ +-use std::path::Path; +-use serde::{Deserialize, Serialize}; +-use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation }; + use attestation_types::Claims; ++use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation}; ++use serde::{Deserialize, Serialize}; ++use std::path::Path; + + #[derive(thiserror::Error, Debug)] + pub enum VerifyError { +@@ -28,9 +28,9 @@ pub enum VerifyError { + + #[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, ++ 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 { +@@ -41,13 +41,11 @@ impl Default for TokenVerifyConfig { + } + } + } +-pub struct TokenVerifier +-{ ++pub struct TokenVerifier { + pub config: TokenVerifyConfig, + } + +-impl Default for TokenVerifier +-{ ++impl Default for TokenVerifier { + fn default() -> Self { + TokenVerifier { + config: TokenVerifyConfig::default(), +@@ -66,32 +64,33 @@ 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{ ++ 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 +- { ++ 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 { ++ pub fn verify(&self, token: &String) -> Result { + let header = decode_header(&token)?; + let alg: Algorithm = header.alg; + + if !Self::support_rs(&alg) && !Self::support_ps(&alg) { +- return Err(VerifyError::UnknownAlg(format!("unknown algrithm {:?}", alg))); ++ return Err(VerifyError::UnknownAlg(format!( ++ "unknown algorithm {:?}", ++ alg ++ ))); + } + if !Path::new(&self.config.cert).exists() { +- return Err(VerifyError::CertNotExist(format!("{:?} not exist", self.config.cert))); ++ return Err(VerifyError::CertNotExist(format!( ++ "{:?} not exist", ++ self.config.cert ++ ))); + } + let cert = std::fs::read(&self.config.cert).unwrap(); + +diff --git a/service/attestation/attestation-client/Cargo.toml b/service/attestation/attestation-client/Cargo.toml +new file mode 100644 +index 0000000..e5a068a +--- /dev/null ++++ b/service/attestation/attestation-client/Cargo.toml +@@ -0,0 +1,16 @@ ++[package] ++name = "attestation-client" ++version = "0.1.0" ++edition = "2021" ++ ++# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html ++ ++[dependencies] ++async-trait = "0.1.85" ++clap = { version = "4.5.24", features = ["derive", "std"] } ++reqwest = { version = "0.12.5", features = ["blocking", "cookies", "json"] } ++thiserror = "2.0.10" ++http = "1.2.0" ++tokio = { version = "1.43.0", features = ["full"] } ++attestation-types = { path = "../attestation-types" } ++serde_json = "1.0.135" +diff --git a/service/attestation/attestation-client/src/client.rs b/service/attestation/attestation-client/src/client.rs +new file mode 100644 +index 0000000..2c0f139 +--- /dev/null ++++ b/service/attestation/attestation-client/src/client.rs +@@ -0,0 +1,53 @@ ++/* ++ * 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. ++ */ ++ ++//! Common web request client ++ ++use crate::error::Result; ++use reqwest::Client; ++ ++const DEFAULT_AS_ADDRESS: &str = "127.0.0.1:8080"; ++ ++pub(crate) enum Protocal { ++ Http { svr: String }, ++ // Https { svr: String, cert: String }, ++} ++ ++pub(crate) struct AsClient { ++ protocal: Protocal, ++ client: Client, ++} ++ ++impl AsClient { ++ pub(crate) fn new(cookie_store: bool, protocal: Protocal) -> Result { ++ let client = match &protocal { ++ Protocal::Http { svr } => Client::builder().cookie_store(cookie_store).build()?, ++ }; ++ ++ Ok(Self { protocal, client }) ++ } ++ ++ pub(crate) fn default() -> Self { ++ let svr = std::env::var("AS_ADDRESS").unwrap_or(DEFAULT_AS_ADDRESS.to_string()); ++ AsClient::new(false, Protocal::Http { svr }).unwrap() ++ } ++ ++ pub(crate) fn base_url(&self) -> String { ++ match &self.protocal { ++ Protocal::Http { svr } => format!("http://{}", svr), ++ } ++ } ++ ++ pub(crate) fn client(&self) -> Client { ++ self.client.clone() ++ } ++} +diff --git a/service/attestation/attestation-client/src/error.rs b/service/attestation/attestation-client/src/error.rs +new file mode 100644 +index 0000000..2952de2 +--- /dev/null ++++ b/service/attestation/attestation-client/src/error.rs +@@ -0,0 +1,23 @@ ++/* ++ * 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; ++ ++pub type Result = std::result::Result; ++ ++#[derive(Error, Debug)] ++#[non_exhaustive] ++pub enum ClientError { ++ #[error("reqwest error: {0}")] ++ ReqwestError(#[from] reqwest::Error), ++ #[error("Http error {0}: {1}")] ++ HttpError(String, http::status::StatusCode), ++} +diff --git a/service/attestation/attestation-client/src/main.rs b/service/attestation/attestation-client/src/main.rs +new file mode 100644 +index 0000000..a779a71 +--- /dev/null ++++ b/service/attestation/attestation-client/src/main.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. ++ */ ++ ++//! This is the client tool for attestation service, which encapsulates frequently-used web request ++//! into the sub-command of the command line tool. ++ ++mod client; ++mod error; ++mod resource; ++mod resource_policy; ++ ++use crate::resource::ResourceArgs; ++use crate::resource_policy::ResourcePolicyArgs; ++use clap::{Parser, Subcommand}; ++use client::AsClient; ++ ++/// A fictional versioning CLI ++#[derive(Debug, Parser)] // requires `derive` feature ++#[command(name = "attestation-client")] ++#[command(about = "Web client of attestation service", long_about = None)] ++struct Cli { ++ #[command(subcommand)] ++ command: Commands, ++} ++ ++#[derive(Debug, Subcommand)] ++enum Commands { ++ Resource(ResourceArgs), ++ ResourcePolicy(ResourcePolicyArgs), ++} ++ ++fn main() { ++ let args = Cli::parse(); ++ let client = AsClient::default(); ++ match args.command { ++ Commands::Resource(args) => { ++ args.process(client); ++ } ++ Commands::ResourcePolicy(args) => { ++ args.process(client); ++ } ++ } ++} +diff --git a/service/attestation/attestation-client/src/resource/client.rs b/service/attestation/attestation-client/src/resource/client.rs +new file mode 100644 +index 0000000..e0dcb08 +--- /dev/null ++++ b/service/attestation/attestation-client/src/resource/client.rs +@@ -0,0 +1,234 @@ ++/* ++ * 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. ++ */ ++ ++//! Implement web request for resource to attestation service ++ ++use crate::client::AsClient; ++use crate::error::{ClientError, Result}; ++use attestation_types::{ ++ resource::ResourceLocation, ++ service::{GetResourceOp, SetResourceOp, SetResourceRequest}, ++}; ++use reqwest::Client; ++ ++pub(crate) struct ResourceClient { ++ client: AsClient, ++} ++ ++impl ResourceClient { ++ pub(crate) fn new(client: AsClient) -> Self { ++ Self { client } ++ } ++ ++ fn endpoint(&self) -> String { ++ format!("{}/resource/storage", self.client.base_url()) ++ } ++ ++ fn client(&self) -> Client { ++ self.client.client() ++ } ++ ++ pub(crate) async fn vendor_get_resource(&self, vendor: &str) -> Result> { ++ let payload = GetResourceOp::VendorGet { ++ vendor: vendor.to_string(), ++ }; ++ ++ let res = self ++ .client() ++ .get(self.endpoint()) ++ .header("Content-Type", "application/json") ++ .json(&payload) ++ .send() ++ .await?; ++ let status = res.status(); ++ if status.is_success() { ++ Ok(res.json().await?) ++ } else { ++ Err(ClientError::HttpError( ++ format!("failed to get resource: {}", res.text().await?), ++ status, ++ )) ++ } ++ } ++ ++ pub(crate) async fn vendor_add_resource( ++ &self, ++ vendor: &str, ++ path: &str, ++ content: &str, ++ policy: &Vec, ++ ) -> Result { ++ let op = SetResourceOp::Add { ++ content: content.to_string(), ++ policy: policy.clone(), ++ }; ++ let payload = SetResourceRequest { ++ op, ++ resource: ResourceLocation::new(Some(vendor.to_string()), path.to_string()), ++ }; ++ let res = self ++ .client() ++ .post(self.endpoint()) ++ .header("Content-Type", "application/json") ++ .json(&payload) ++ .send() ++ .await?; ++ let status = res.status(); ++ if status.is_success() { ++ Ok(res.text().await?) ++ } else { ++ Err(ClientError::HttpError( ++ format!("failed to add resource: {}", res.text().await?), ++ status, ++ )) ++ } ++ } ++ ++ pub(crate) async fn vendor_delete_resource(&self, vendor: &str, path: &str) -> Result { ++ let op = SetResourceOp::Delete; ++ let payload = SetResourceRequest { ++ op, ++ resource: ResourceLocation::new(Some(vendor.to_string()), path.to_string()), ++ }; ++ let res = self ++ .client() ++ .post(self.endpoint()) ++ .header("Content-Type", "application/json") ++ .json(&payload) ++ .send() ++ .await?; ++ let status = res.status(); ++ if status.is_success() { ++ Ok(res.text().await?) ++ } else { ++ Err(ClientError::HttpError( ++ format!("failed to delete resource: {}", res.text().await?), ++ status, ++ )) ++ } ++ } ++ ++ pub(crate) async fn vendor_modify_resource( ++ &self, ++ vendor: &str, ++ path: &str, ++ content: &str, ++ ) -> Result { ++ let op = SetResourceOp::Modify { ++ content: content.to_string(), ++ }; ++ let payload = SetResourceRequest { ++ op, ++ resource: ResourceLocation::new(Some(vendor.to_string()), path.to_string()), ++ }; ++ let res = self ++ .client() ++ .post(self.endpoint()) ++ .header("Content-Type", "application/json") ++ .json(&payload) ++ .send() ++ .await?; ++ let status = res.status(); ++ if status.is_success() { ++ Ok(res.text().await?) ++ } else { ++ Err(ClientError::HttpError( ++ format!("failed to modify resource: {}", res.text().await?), ++ status, ++ )) ++ } ++ } ++ ++ pub(crate) async fn vendor_bind_resource( ++ &self, ++ vendor: &str, ++ path: &str, ++ policy: &Vec, ++ ) -> Result { ++ let op = SetResourceOp::Bind { ++ policy: policy.clone(), ++ }; ++ let payload = SetResourceRequest { ++ op, ++ resource: ResourceLocation::new(Some(vendor.to_string()), path.to_string()), ++ }; ++ let res = self ++ .client() ++ .post(self.endpoint()) ++ .header("Content-Type", "application/json") ++ .json(&payload) ++ .send() ++ .await?; ++ let status = res.status(); ++ if status.is_success() { ++ Ok(res.text().await?) ++ } else { ++ Err(ClientError::HttpError( ++ format!("failed to bind resource: {}", res.text().await?), ++ status, ++ )) ++ } ++ } ++ ++ pub(crate) async fn vendor_unbind_resource( ++ &self, ++ vendor: &str, ++ path: &str, ++ policy: &Vec, ++ ) -> Result { ++ let op = SetResourceOp::Unbind { ++ policy: policy.clone(), ++ }; ++ let payload = SetResourceRequest { ++ op, ++ resource: ResourceLocation::new(Some(vendor.to_string()), path.to_string()), ++ }; ++ let res = self ++ .client() ++ .post(self.endpoint()) ++ .header("Content-Type", "application/json") ++ .json(&payload) ++ .send() ++ .await?; ++ let status = res.status(); ++ if status.is_success() { ++ Ok(res.text().await?) ++ } else { ++ Err(ClientError::HttpError( ++ format!("failed to unbind resource: {}", res.text().await?), ++ status, ++ )) ++ } ++ } ++} ++ ++// async fn get_challenge() { ++// let challenge_endpoint = format!("{}/challenge", self.config.svr_url); ++// let client = self.create_client(self.config.protocal.clone(), true)?; ++// let res = client ++// .get(challenge_endpoint) ++// .header("Content-Type", "application/json") ++// .header("content-length", 0) ++// .send() ++// .await?; ++// let challenge = match res.status() { ++// reqwest::StatusCode::OK => { ++// let respone: String = res.json().await.unwrap(); ++// log::debug!("get challenge success, AS Response: {:?}", respone); ++// respone ++// } ++// status => { ++// log::error!("get challenge Failed, AS Response: {:?}", status); ++// bail!("get challenge Failed") ++// } ++// }; ++// } +diff --git a/service/attestation/attestation-client/src/resource/mod.rs b/service/attestation/attestation-client/src/resource/mod.rs +new file mode 100644 +index 0000000..d198ef4 +--- /dev/null ++++ b/service/attestation/attestation-client/src/resource/mod.rs +@@ -0,0 +1,129 @@ ++/* ++ * 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. ++ */ ++ ++//! Subcommand for getting or setting resource. ++//! ++ ++pub(crate) mod client; ++ ++use self::client::ResourceClient; ++use crate::client::AsClient; ++use clap::{Args, Subcommand}; ++ ++#[derive(Debug, Args)] ++#[command(args_conflicts_with_subcommands = true)] ++#[command(flatten_help = true)] ++pub(crate) struct ResourceArgs { ++ #[command(subcommand)] ++ pub(crate) command: ResourceCommand, ++} ++ ++#[derive(Debug, Subcommand)] ++pub(crate) enum ResourceCommand { ++ Get { ++ vendor: String, ++ }, ++ Add { ++ vendor: String, ++ path: String, ++ content: String, ++ policy: Vec, ++ }, ++ Delete { ++ vendor: String, ++ path: String, ++ }, ++ Modify { ++ vendor: String, ++ path: String, ++ content: String, ++ }, ++ BindPolicy { ++ vendor: String, ++ path: String, ++ policy: Vec, ++ }, ++ UnbindPolicy { ++ vendor: String, ++ path: String, ++ policy: Vec, ++ }, ++} ++ ++impl ResourceArgs { ++ pub(crate) fn process(&self, base_client: AsClient) { ++ self.command.dispatch(base_client); ++ } ++} ++ ++impl ResourceCommand { ++ fn dispatch(&self, base_client: AsClient) { ++ let client = ResourceClient::new(base_client); ++ let runtime = tokio::runtime::Runtime::new().unwrap(); ++ ++ match self { ++ ResourceCommand::Get { vendor } => { ++ let ret = runtime ++ .block_on(client.vendor_get_resource(vendor)) ++ .unwrap(); ++ println!("{:?}", ret); ++ } ++ ResourceCommand::Add { ++ vendor, ++ path, ++ content, ++ policy, ++ } => { ++ let ret = runtime ++ .block_on(client.vendor_add_resource(vendor, path, content, policy)) ++ .unwrap(); ++ println!("{:?}", ret); ++ } ++ ResourceCommand::Delete { vendor, path } => { ++ let ret = runtime ++ .block_on(client.vendor_delete_resource(vendor, path)) ++ .unwrap(); ++ println!("{:?}", ret); ++ } ++ ResourceCommand::Modify { ++ vendor, ++ path, ++ content, ++ } => { ++ let ret = runtime ++ .block_on(client.vendor_modify_resource(vendor, path, content)) ++ .unwrap(); ++ println!("{:?}", ret); ++ } ++ ResourceCommand::BindPolicy { ++ vendor, ++ path, ++ policy, ++ } => { ++ let ret = runtime ++ .block_on(client.vendor_bind_resource(vendor, path, policy)) ++ .unwrap(); ++ println!("{:?}", ret); ++ } ++ ResourceCommand::UnbindPolicy { ++ vendor, ++ path, ++ policy, ++ } => { ++ let ret = runtime ++ .block_on(client.vendor_unbind_resource(vendor, path, policy)) ++ .unwrap(); ++ println!("{:?}", ret); ++ } ++ } ++ } ++} +diff --git a/service/attestation/attestation-client/src/resource_policy/client.rs b/service/attestation/attestation-client/src/resource_policy/client.rs +new file mode 100644 +index 0000000..582a6bd +--- /dev/null ++++ b/service/attestation/attestation-client/src/resource_policy/client.rs +@@ -0,0 +1,190 @@ ++/* ++ * 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. ++ */ ++ ++//! Implement web request for resource policy to attestation service ++//! ++ ++use crate::client::AsClient; ++use crate::error::{ClientError, Result}; ++use attestation_types::{ ++ resource::policy::PolicyLocation, ++ service::{GetResourcePolicyOp, SetResourcePolicyOp}, ++}; ++use reqwest::Client; ++ ++pub(crate) struct ResourcePolicyClient { ++ client: AsClient, ++} ++ ++impl ResourcePolicyClient { ++ pub(crate) fn new(client: AsClient) -> Self { ++ Self { client } ++ } ++ ++ fn endpoint(&self) -> String { ++ format!("{}/resource/policy", self.client.base_url()) ++ } ++ ++ fn client(&self) -> Client { ++ self.client.client() ++ } ++ ++ pub(crate) async fn vendor_get_one(&self, vendor: &str, id: &str) -> Result { ++ let payload = GetResourcePolicyOp::GetOne { ++ policy: PolicyLocation { ++ vendor: Some(vendor.to_string()), ++ id: id.to_string(), ++ }, ++ }; ++ ++ let res = self ++ .client() ++ .get(self.endpoint()) ++ .header("Content-Type", "application/json") ++ .json(&payload) ++ .send() ++ .await?; ++ let status = res.status(); ++ if status.is_success() { ++ Ok(res.text().await?) ++ } else { ++ Err(ClientError::HttpError( ++ format!("failed to get resource policy: {}", res.text().await?), ++ status, ++ )) ++ } ++ } ++ pub(crate) async fn vendor_get_all(&self) -> Result> { ++ let payload = GetResourcePolicyOp::GetAll; ++ ++ let res = self ++ .client() ++ .get(self.endpoint()) ++ .header("Content-Type", "application/json") ++ .json(&payload) ++ .send() ++ .await?; ++ let status = res.status(); ++ if status.is_success() { ++ Ok(res.json().await?) ++ } else { ++ Err(ClientError::HttpError( ++ format!("failed to get all resource policy: {}", res.text().await?), ++ status, ++ )) ++ } ++ } ++ pub(crate) async fn vendor_get_all_in_vendor(&self, vendor: &str) -> Result> { ++ let payload = GetResourcePolicyOp::GetAllInVendor { ++ vendor: vendor.to_string(), ++ }; ++ ++ let res = self ++ .client() ++ .get(self.endpoint()) ++ .header("Content-Type", "application/json") ++ .json(&payload) ++ .send() ++ .await?; ++ let status = res.status(); ++ if status.is_success() { ++ Ok(res.json().await?) ++ } else { ++ Err(ClientError::HttpError( ++ format!( ++ "failed to get all resource policy in vendor {}: {}", ++ vendor, ++ res.text().await? ++ ), ++ status, ++ )) ++ } ++ } ++ pub(crate) async fn vendor_add(&self, vendor: &str, id: &str, content: &str) -> Result { ++ let payload = SetResourcePolicyOp::Add { ++ policy: PolicyLocation { ++ vendor: Some(vendor.to_string()), ++ id: id.to_string(), ++ }, ++ content: content.to_string(), ++ }; ++ ++ let res = self ++ .client() ++ .post(self.endpoint()) ++ .header("Content-Type", "application/json") ++ .json(&payload) ++ .send() ++ .await?; ++ let status = res.status(); ++ if status.is_success() { ++ Ok(res.text().await?) ++ } else { ++ Err(ClientError::HttpError( ++ format!("failed to add resource policy: {}", res.text().await?), ++ status, ++ )) ++ } ++ } ++ pub(crate) async fn vendor_delete(&self, vendor: &str, id: &str) -> Result { ++ let payload = SetResourcePolicyOp::Delete { ++ policy: PolicyLocation { ++ vendor: Some(vendor.to_string()), ++ id: id.to_string(), ++ }, ++ }; ++ ++ let res = self ++ .client() ++ .post(self.endpoint()) ++ .header("Content-Type", "application/json") ++ .json(&payload) ++ .send() ++ .await?; ++ let status = res.status(); ++ if status.is_success() { ++ Ok(res.text().await?) ++ } else { ++ Err(ClientError::HttpError( ++ format!("failed to delete resource policy: {}", res.text().await?), ++ status, ++ )) ++ } ++ } ++ ++ pub(crate) async fn vendor_clear_all(&self, vendor: &str) -> Result { ++ let payload = SetResourcePolicyOp::ClearAll { ++ vendor: vendor.to_string(), ++ }; ++ ++ let res = self ++ .client() ++ .post(self.endpoint()) ++ .header("Content-Type", "application/json") ++ .json(&payload) ++ .send() ++ .await?; ++ let status = res.status(); ++ if status.is_success() { ++ Ok(res.text().await?) ++ } else { ++ Err(ClientError::HttpError( ++ format!( ++ "failed to clear resource policy in vendor {}: {}", ++ vendor, ++ res.text().await? ++ ), ++ status, ++ )) ++ } ++ } ++} +diff --git a/service/attestation/attestation-client/src/resource_policy/mod.rs b/service/attestation/attestation-client/src/resource_policy/mod.rs +new file mode 100644 +index 0000000..4879412 +--- /dev/null ++++ b/service/attestation/attestation-client/src/resource_policy/mod.rs +@@ -0,0 +1,100 @@ ++/* ++ * 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. ++ */ ++ ++//! Subcommand for getting or setting resource policy. ++//! ++ ++pub(crate) mod client; ++ ++use self::client::ResourcePolicyClient; ++use crate::client::AsClient; ++use clap::{Args, Subcommand}; ++ ++#[derive(Debug, Args)] ++#[command(args_conflicts_with_subcommands = true)] ++#[command(flatten_help = true)] ++pub(crate) struct ResourcePolicyArgs { ++ #[command(subcommand)] ++ pub(crate) command: ResourcePolicyCommand, ++} ++ ++#[derive(Debug, Subcommand)] ++pub(crate) enum ResourcePolicyCommand { ++ GetOne { ++ vendor: String, ++ id: String, ++ }, ++ GetAll, ++ GetAllInVendor { ++ vendor: String, ++ }, ++ Add { ++ vendor: String, ++ id: String, ++ content: String, ++ }, ++ Delete { ++ vendor: String, ++ id: String, ++ }, ++ ClearAll { ++ vendor: String, ++ }, ++} ++ ++impl ResourcePolicyArgs { ++ pub(crate) fn process(&self, base_client: AsClient) { ++ self.command.dispatch(base_client); ++ } ++} ++ ++impl ResourcePolicyCommand { ++ fn dispatch(&self, base_client: AsClient) { ++ let client = ResourcePolicyClient::new(base_client); ++ let runtime = tokio::runtime::Runtime::new().unwrap(); ++ ++ match self { ++ ResourcePolicyCommand::GetOne { vendor, id } => { ++ let ret = runtime.block_on(client.vendor_get_one(vendor, id)).unwrap(); ++ println!("{}", ret); ++ } ++ ResourcePolicyCommand::GetAll => { ++ let ret = runtime.block_on(client.vendor_get_all()).unwrap(); ++ println!("{}", serde_json::json!(ret).to_string()); ++ } ++ ResourcePolicyCommand::GetAllInVendor { vendor } => { ++ let ret = runtime ++ .block_on(client.vendor_get_all_in_vendor(vendor)) ++ .unwrap(); ++ println!("{}", serde_json::json!(ret).to_string()); ++ } ++ ResourcePolicyCommand::Add { ++ vendor, ++ id, ++ content, ++ } => { ++ let ret = runtime ++ .block_on(client.vendor_add(vendor, id, content)) ++ .unwrap(); ++ println!("{}", ret); ++ } ++ ResourcePolicyCommand::Delete { vendor, id } => { ++ let ret = runtime.block_on(client.vendor_delete(vendor, id)).unwrap(); ++ println!("{}", ret); ++ } ++ ResourcePolicyCommand::ClearAll { vendor } => { ++ let ret = runtime.block_on(client.vendor_clear_all(vendor)).unwrap(); ++ println!("{}", ret); ++ } ++ } ++ } ++} +diff --git a/service/attestation/attestation-service/Cargo.toml b/service/attestation/attestation-service/Cargo.toml +index cf0dd87..ed0ebd2 100644 +--- a/service/attestation/attestation-service/Cargo.toml ++++ b/service/attestation/attestation-service/Cargo.toml +@@ -1,19 +1,13 @@ + [workspace] + resolver = "2" +-members = [ +- "service", +- "verifier", +- "token", +- "reference", +- "policy", +- "tests" +-] ++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" ++async-recursion = "1.1.1" + cose-rust = "0.1.7" + ciborium = "0.2.2" + hex = "0.4" +@@ -24,7 +18,8 @@ rand = "0.8.5" + ima-measurements = "0.2.0" + fallible-iterator = "0.2.0" + +-actix-web = "4.5" ++actix-web = { version = "4.5.0", features = ["openssl"] } ++actix-web-httpauth = "0.8.2" + env_logger = "0.9" + tokio = { version = "1", features = ["full"] } + strum = { version = "0.25", features = ["derive"] } +@@ -39,4 +34,6 @@ lazy_static = "1.5.0" + uuid = { version = "1.2.2", features = ["serde", "v4"] } + scc = "2.1" + +-attestation-types = {path = "../attestation-types"} ++attestation-types = { path = "../attestation-types" } ++ear = "0.1.1" ++ccatoken = "0.1.0" +diff --git a/service/attestation/attestation-service/README.md b/service/attestation/attestation-service/README.md +index c64e6f1..6443ab2 100644 +--- a/service/attestation/attestation-service/README.md ++++ b/service/attestation/attestation-service/README.md +@@ -2,5 +2,20 @@ + 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 ++# Quick Start ++## Start Attestation Service quickly ++update repository source config ++``` ++vim /etc/yum.repos.d/openEuler.repo ++[everything] ++name=everything ++baseurl=https://repo.openeuler.org/openEuler-24.09/everything/aarch64/ ++enabled=1 ++gpgcheck=0 ++ ++//run service in current host like this, initialize environment automatically ++./as_startup.sh ++ ++//or in docker and specified ip:port ++./as_startup.sh -t docker -l 127.0.0.1:8080 ++``` +diff --git a/service/attestation/attestation-service/as_startup.sh b/service/attestation/attestation-service/as_startup.sh +new file mode 100755 +index 0000000..3a7e9fa +--- /dev/null ++++ b/service/attestation/attestation-service/as_startup.sh +@@ -0,0 +1,138 @@ ++#!/usr/bin/env bash ++DOCKER_TAR="openEuler-docker.aarch64.tar.xz" ++OPENEULER_DOCKER_URL="http://121.36.84.172/dailybuild/EBS-openEuler-24.09/rc5_openeuler-2024-09-12-18-14-43/docker_img/aarch64/${DOCKER_TAR}" ++IMAGE_NAME="openeuler-24.09" ++CONTAINER_NAME="openeuler-2409" ++PRIVATE_KEY="private.pem" ++CSR="server.csr" ++CERT="as_cert.pem" ++DIR_TMP="tmp" ++SERVICE_NAME="attestation-service" ++ ++generate_config() { ++ if [ -d ${DIR_TMP} ]; then ++ cd ${DIR_TMP} ++ if [ -a ${CERT} ] && [ -a ${PRIVATE_KEY} ]; then ++ echo "configuration already exist in ${DIR_TMP}, reuse it" ++ return ++ else ++ echo "${DIR_TMP} exist but broken, rename or delete it" ++ exit 1 ++ fi ++ fi ++ mkdir ${DIR_TMP} ++ cd ${DIR_TMP} ++ ++ openssl genrsa -out ${PRIVATE_KEY} 2048 ++ openssl req -subj "/C=CN/ST=ST/L=CITY/O=Company/CN=test.com" -new -key ${PRIVATE_KEY} -out ${CSR} ++ openssl x509 -req -in ${CSR} -out ${CERT} -signkey ${PRIVATE_KEY} -days 3650 ++ ++ echo "config files generated in ${DIR_TMP}" ++} ++ ++setup() { ++ mkdir -p /etc/attestation/attestation-agent/ ++ mkdir -p /etc/attestation/attestation-service/token ++ cp ${CERT} /etc/attestation/attestation-agent/ ++ cp ${PRIVATE_KEY} /etc/attestation/attestation-service/token ++ yum install secGear-as -y ++} ++ ++as_start_in_host() { ++ listen_at=$1 ++ setup ++ /usr/bin/${SERVICE_NAME} -s ${listen_at} 2>&1 & ++} ++ ++start_container() { ++ wget -V||yum install wget ++ docker -v||yum install docker ++ docker images | grep -E "^${IMAGE_NAME}[ ]" ++ if [ $? -ne 0 ]; then ++ wget ${OPENEULER_DOCKER_URL} ++ docker load -i ${DOCKER_TAR} ++ fi ++ ++ docker ps -a | grep -E "\s${CONTAINER_NAME}$" ++ if [ $? -eq 0 ]; then ++ echo "Error: container ${CONTAINER_NAME} already exist, please delete it or rename it" ++ echo -e "\tdelete command:docker rm ${CONTAINER_NAME} --force" ++ echo -e "\trename command:docker rename ${CONTAINER_NAME} {any_name_you_want}" ++ exit 1 ++ fi ++ docker run -d --name ${CONTAINER_NAME} --network host ${IMAGE_NAME}:latest /bin/bash -c "while true; do sleep 1;done" ++} ++ ++setup_container() { ++ docker exec ${CONTAINER_NAME} mkdir -p /etc/attestation/attestation-agent/ ++ docker exec ${CONTAINER_NAME} mkdir -p /etc/attestation/attestation-service/token ++ docker cp ${CERT} ${CONTAINER_NAME}:/etc/attestation/attestation-agent/ ++ docker cp ${PRIVATE_KEY} ${CONTAINER_NAME}:/etc/attestation/attestation-service/token ++ yum download kunpengsecl-attester kunpengsecl-qcaserver secGear-as cjson compat-openssl11-libs ++ ls *.rpm | xargs -i docker cp {} ${CONTAINER_NAME}:/home ++ docker exec ${CONTAINER_NAME} rpm -ivh /home/*.rpm ++} ++ ++as_start_in_docker() { ++ start_container ++ setup_container ++ docker exec -d ${CONTAINER_NAME} /bin/bash -c "/usr/bin/${SERVICE_NAME} -s ${listen_at} 2>&1" ++} ++ ++start_attestation_service() { ++ run_in=$1 ++ listen_at=$2 ++ generate_config ++ if [[ ${run_in} == "host" ]]; then ++ as_start_in_host ${listen_at} ++ else ++ as_start_in_docker ${listen_at} ++ fi ++ echo ${SERVICE_NAME} started ${listen_at} ++} ++ ++ ++print_usage() { ++ echo "Usage: ./as_startup [-t docker|host] [-l 127.0.0.1:8080]" ++ echo "example: ./as_startup run in host,listen at 127.0.0.1:8080 by default" ++} ++ ++run_in="" ++listen_at="" ++while getopts "t:l:h" optname ++ do ++ case "$optname" in ++ "t") ++ run_in="$OPTARG" ++ echo "run in $run_in" ++ ;; ++ "l") ++ listen_at="$OPTARG" ++ echo "listen at $listen_at" ++ ;; ++ "h") ++ print_usage ++ exit 1 ++ ;; ++ *) ++ print_usage ++ exit 1 ++ ;; ++ esac ++ done ++if [[ ${run_in} == "" ]]; then ++ run_in="host" ++fi ++ ++if [[ ${listen_at} == "" ]]; then ++ listen_at="127.0.0.1:8080" ++fi ++ ++service_run=$(ps aux | grep "/usr/bin/${SERVICE_NAME}" | wc -l) ++if [ ${service_run} -gt 1 ]; then ++ echo "${SERVICE_NAME} already run" ++ exit 1 ++fi ++ ++echo "${SERVICE_NAME} run in $run_in, listen at $listen_at" ++start_attestation_service ${run_in} ${listen_at} +diff --git a/service/attestation/attestation-service/policy/Cargo.toml b/service/attestation/attestation-service/policy/Cargo.toml +index 87917a4..acf961f 100644 +--- a/service/attestation/attestation-service/policy/Cargo.toml ++++ b/service/attestation/attestation-service/policy/Cargo.toml +@@ -10,3 +10,4 @@ regorus.workspace = true + base64.workspace = true + tokio.workspace = true + futures.workspace = true ++async-trait.workspace = true +diff --git a/service/attestation/attestation-service/policy/src/lib.rs b/service/attestation/attestation-service/policy/src/lib.rs +index 0677f45..f63146a 100644 +--- a/service/attestation/attestation-service/policy/src/lib.rs ++++ b/service/attestation/attestation-service/policy/src/lib.rs +@@ -105,7 +105,9 @@ output["Other"] := "other" if { + ); + let data = String::new(); + let policy_id: Vec = vec![]; +- let result = engine.evaluate(&String::from("vcca"), &refs_from_report, &data, &policy_id).await; ++ let result = engine ++ .evaluate(&String::from("vcca"), &refs_from_report, &data, &policy_id) ++ .await; + println!("{:?}", result); + assert!(result.is_ok()); + match result { +@@ -165,7 +167,9 @@ output["Other"] := "other" if { + ); + 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; ++ let result = engine ++ .evaluate(&String::from("vcca"), &refs_from_report, &data, &policy_id) ++ .await; + assert!(result.is_ok()); + match result { + Ok(ret) => { +diff --git a/service/attestation/attestation-service/policy/src/opa/mod.rs b/service/attestation/attestation-service/policy/src/opa/mod.rs +index c2e1cdb..7ce7f86 100644 +--- a/service/attestation/attestation-service/policy/src/opa/mod.rs ++++ b/service/attestation/attestation-service/policy/src/opa/mod.rs +@@ -45,7 +45,9 @@ impl PolicyEngine for OPA { + } else if tee == "itrustee" { + policy_id_used.push(String::from(DEFAULT_ITRUSTEE_REGO)); + } else { +- return Err(PolicyEngineError::TeeTypeUnknown(format!("tee type unknown: {tee}"))); ++ return Err(PolicyEngineError::TeeTypeUnknown(format!( ++ "tee type unknown: {tee}" ++ ))); + } + policy_path = self.default_policy_dir.clone(); + } else { +@@ -56,13 +58,17 @@ impl PolicyEngine for OPA { + 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 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)) +- })?; ++ 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)) +@@ -74,7 +80,10 @@ impl PolicyEngine for OPA { + PolicyEngineError::EngineLoadDataError(format!("data to Value failed: {}", err)) + })?; + engine.add_data(data).map_err(|err| { +- PolicyEngineError::EngineLoadDataError(format!("engine add data failed: {}", err)) ++ PolicyEngineError::EngineLoadDataError(format!( ++ "engine add data failed: {}", ++ err ++ )) + })?; + } + +@@ -94,32 +103,41 @@ impl PolicyEngine for OPA { + ) -> Result<(), PolicyEngineError> { + let raw = base64::engine::general_purpose::URL_SAFE_NO_PAD + .decode(policy) +- .map_err(|err| PolicyEngineError::InvalidPolicy(format!("policy decode failed: {}", err)))?; ++ .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)))?; ++ .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)))?; ++ .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)))? +- { ++ 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)) +- })?; ++ 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( +@@ -142,7 +160,9 @@ impl PolicyEngine for OPA { + 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)))?; ++ .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) + } +@@ -153,7 +173,10 @@ impl OPA { + 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)) ++ PolicyEngineError::CreatePolicyDirError(format!( ++ "policy dir create failed: {}", ++ err ++ )) + })?; + } + +diff --git a/service/attestation/attestation-service/reference/Cargo.toml b/service/attestation/attestation-service/reference/Cargo.toml +index fb0a4bb..664f745 100644 +--- a/service/attestation/attestation-service/reference/Cargo.toml ++++ b/service/attestation/attestation-service/reference/Cargo.toml +@@ -14,4 +14,4 @@ sled.workspace = true + openssl.workspace = true + hex.workspace = true + lazy_static.workspace = true +-thiserror.workspace = true +\ No newline at end of file ++thiserror.workspace = true +diff --git a/service/attestation/attestation-service/reference/src/lib.rs b/service/attestation/attestation-service/reference/src/lib.rs +index 4347fc1..aa1a6c7 100644 +--- a/service/attestation/attestation-service/reference/src/lib.rs ++++ b/service/attestation/attestation-service/reference/src/lib.rs +@@ -9,10 +9,10 @@ + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ ++mod extractor; + pub mod local_fs; + pub mod reference; + pub mod store; +-mod extractor; + + #[cfg(test)] + mod tests { +@@ -120,9 +120,17 @@ mod tests { + //key + let key = format!("ref{}", i); + //value +- let value:String = rng.clone().sample_iter(&Alphanumeric).take(128).map(char::from).collect(); ++ 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)); ++ 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); +@@ -133,9 +141,10 @@ mod tests { + for hd in thread_all { + match hd.join() { + Ok(_) => {} +- Err(_) => {assert!(false)} ++ 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 +index 1e03579..2220fc0 100644 +--- a/service/attestation/attestation-service/reference/src/local_fs/mod.rs ++++ b/service/attestation/attestation-service/reference/src/local_fs/mod.rs +@@ -10,13 +10,12 @@ + * 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 std::sync::Arc; + + use crate::store::{KvError, KvStore}; + +- + pub struct LocalFs { + db: Arc, + } +@@ -24,7 +23,8 @@ pub struct LocalFs { + impl Default for LocalFs { + fn default() -> Self { + lazy_static! { +- static ref db_handle: Arc = Arc::new(sled::open("/etc/attestation/attestation-service/reference").unwrap()); ++ static ref db_handle: Arc = ++ Arc::new(sled::open("/etc/attestation/attestation-service/reference").unwrap()); + } + LocalFs { + db: db_handle.clone(), +diff --git a/service/attestation/attestation-service/reference/src/reference/mod.rs b/service/attestation/attestation-service/reference/src/reference/mod.rs +index 6ec4371..c400683 100644 +--- a/service/attestation/attestation-service/reference/src/reference/mod.rs ++++ b/service/attestation/attestation-service/reference/src/reference/mod.rs +@@ -46,7 +46,7 @@ pub enum RefOpError { + #[error("reference operation error {0}")] + Err(String), + #[error("reference store error: {0:?}")] +- StoreErr(#[from] KvError) ++ StoreErr(#[from] KvError), + } + + impl ReferenceOps { +diff --git a/service/attestation/attestation-service/reference/src/store/mod.rs b/service/attestation/attestation-service/reference/src/store/mod.rs +index c9597f6..a7838e7 100644 +--- a/service/attestation/attestation-service/reference/src/store/mod.rs ++++ b/service/attestation/attestation-service/reference/src/store/mod.rs +@@ -16,7 +16,7 @@ pub enum KvError { + impl std::fmt::Display for KvError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { +- KvError::Err(msg) => write!(f, "kv store error:{}", msg) ++ KvError::Err(msg) => write!(f, "kv store error:{}", msg), + } + } + } +diff --git a/service/attestation/attestation-service/service/Cargo.toml b/service/attestation/attestation-service/service/Cargo.toml +index e8b88b8..ffbe0ed 100644 +--- a/service/attestation/attestation-service/service/Cargo.toml ++++ b/service/attestation/attestation-service/service/Cargo.toml +@@ -10,6 +10,7 @@ hex.workspace = true + serde_json.workspace = true + + actix-web.workspace = true ++actix-web-httpauth.workspace = true + env_logger.workspace = true + tokio.workspace = true + log.workspace = true +@@ -27,9 +28,7 @@ uuid.workspace = true + rand.workspace = true + scc.workspace = true + attestation-types.workspace = true +- +-[dev-dependencies] ++openssl.workspace = true + futures.workspace = true + + [features] +- +diff --git a/service/attestation/attestation-service/service/src/lib.rs b/service/attestation/attestation-service/service/src/lib.rs +index 1c5c907..99ae818 100644 +--- a/service/attestation/attestation-service/service/src/lib.rs ++++ b/service/attestation/attestation-service/service/src/lib.rs +@@ -9,32 +9,43 @@ + * 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}; ++pub mod restapi; ++pub mod result; ++pub mod session; ++ ++use actix_web::web::{self, Data}; ++use anyhow::{anyhow, Context, Result}; ++use attestation_types::resource::admin::simple::SimpleResourceAdmin; ++use attestation_types::resource::admin::ResourceAdminInterface; ++use attestation_types::resource::ResourceLocation; ++use attestation_types::EvlResult; ++use base64_url; ++use futures::lock::Mutex; + use policy::opa::OPA; + use policy::policy_engine::{PolicyEngine, PolicyEngineError}; +-use attestation_types::EvlResult; +- +-pub mod result; ++use rand::RngCore; ++use reference::reference::{RefOpError, ReferenceOps}; ++use serde::{Deserialize, Serialize}; ++use serde_json::Value; ++use session::SessionMap; ++use std::fs::File; ++use std::path::{Path, PathBuf}; ++use std::str::FromStr; ++use std::sync::Arc; ++use token_signer::{EvlReport, TokenSignConfig, TokenSigner}; ++use verifier::{Verifier, VerifierAPIs}; + #[derive(Clone, Debug, Serialize, Deserialize)] + pub struct ASConfig { + pub token_cfg: TokenSignConfig, ++ pub resource_policy: Option, + } + + impl Default for ASConfig { + fn default() -> Self { + Self { + token_cfg: TokenSignConfig::default(), ++ resource_policy: None, + } + } + } +@@ -60,18 +71,22 @@ impl TryFrom<&Path> for ASConfig { + + pub struct AttestationService { + pub config: ASConfig, +- // verify policy sub service +- //policy: , ++ // Resource Administrator ++ pub(crate) resource_admin: Arc>, + // reference value provider sub service + //rvps: , + // tee verifier sub service + //verifier: , ++ // Sessions Map ++ pub(crate) sessions: Data, + } + + impl Default for AttestationService { + fn default() -> Self { + Self { + config: ASConfig::default(), ++ resource_admin: Arc::new(Mutex::new(SimpleResourceAdmin::default())), ++ sessions: web::Data::new(SessionMap::new()), + } + } + } +@@ -88,14 +103,18 @@ impl AttestationService { + ASConfig::default() + } + }; +- Ok(AttestationService {config}) ++ Ok(AttestationService { ++ config, ++ resource_admin: Arc::new(Mutex::new(SimpleResourceAdmin::default())), ++ sessions: web::Data::new(SessionMap::new()), ++ }) + } + /// 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> ++ policy_ids: &Option>, + ) -> Result { + let verifier = Verifier::default(); + let claims_evidence = verifier.verify_evidence(user_data, evidence).await?; +@@ -119,42 +138,69 @@ impl AttestationService { + 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(), ++ Some(policy_id) => policy_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 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!("json value to map fail"))) { +- Err(err) => { return Err(err.unwrap()); } +- Ok(ret) => { ret } ++ let refs = match val ++ .as_object() ++ .ok_or(Err(anyhow!("json value to map fail"))) ++ { ++ 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())); ++ report ++ .as_object_mut() ++ .unwrap() ++ .insert(id.clone(), serde_json::Value::String(eval[id].clone())); + } + } + Err(err) => { + return Err(anyhow!("evaluate error: {err}")); + } + } +- ++ ++ // add ima detail result to report ++ report ++ .as_object_mut() ++ .unwrap() ++ .insert("ima".to_string(), claims_evidence["ima"].clone()); ++ + // issue attestation result token + let evl_report = EvlReport { +- tee: String::from(claims_evidence["tee"].as_str().ok_or(anyhow!("tee type unknown"))?), ++ 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, +@@ -168,53 +214,88 @@ impl AttestationService { + Ok(signer.sign(&evl_report)?) + } + +- pub async fn generate_challenge(&self) -> String { +- let mut nonce: [u8; 32] = [0; 32]; ++ pub async fn generate_challenge(&self, user_data: Option>) -> String { ++ let mut nonce: Vec = vec![0; 32]; + rand::thread_rng().fill_bytes(&mut nonce); ++ if user_data != None { ++ nonce.append(&mut user_data.unwrap()); ++ } + base64_url::encode(&nonce) + } + +- pub async fn set_policy(&self, ++ 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 ++ engine.unwrap().set_policy(id, policy).await + } + +- pub async fn get_all_policy(&self, +- policy_dir: &String, +- ) -> Result { ++ pub async fn get_all_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])); ++ json_obj ++ .as_object_mut() ++ .unwrap() ++ .insert(key.clone(), serde_json::json!(map[key])); + } + Ok(json_obj.to_string()) + } +- Err(err) => Err(err) ++ Err(err) => Err(err), + } + } + +- pub async fn get_policy(&self, ++ pub async fn get_policy( ++ &self, + policy_dir: &String, +- id: &String ++ id: &String, + ) -> Result { + let engine = OPA::new(policy_dir).await?; + Ok(engine.get_policy(id).await?) + } + +- pub async fn register_reference(&self, +- ref_set: &String +- ) -> Result<(), RefOpError> { ++ pub async fn register_reference(&self, ref_set: &String) -> Result<(), RefOpError> { + let mut ops_default = ReferenceOps::default(); + ops_default.register(ref_set) + } ++ ++ pub async fn resource_evaluate(&self, resource: ResourceLocation, claim: &str) -> Result { ++ Ok(self ++ .resource_admin ++ .lock() ++ .await ++ .evaluate_resource(resource, claim) ++ .await ++ .context("fail to evaluate resource according to the claim")?) ++ } ++ ++ pub async fn get_resource(&self, location: ResourceLocation) -> Result { ++ let resource = self ++ .resource_admin ++ .lock() ++ .await ++ .get_resource(location) ++ .await ++ .context("fail to get resource")?; ++ ++ Ok(serde_json::to_string(&resource.get_content())?) ++ } ++ ++ pub async fn list_resource(&self, vendor: &str) -> Result> { ++ self.resource_admin ++ .lock() ++ .await ++ .list_resource(vendor) ++ .await ++ .context("faile to collect resource list in vendor") ++ } ++ ++ pub fn get_sessions(&self) -> Data { ++ self.sessions.clone() ++ } + } +diff --git a/service/attestation/attestation-service/service/src/main.rs b/service/attestation/attestation-service/service/src/main.rs +index 88941b8..d9918f7 100644 +--- a/service/attestation/attestation-service/service/src/main.rs ++++ b/service/attestation/attestation-service/service/src/main.rs +@@ -9,30 +9,32 @@ + * 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 actix_web::{web, App, HttpServer}; + use anyhow::Result; ++use attestation_service::restapi::{ ++ attestation, get_challenge, get_policy, reference, ++ resource::{ ++ policy::{get_resource_policy, set_resource_policy}, ++ storage::{get_resource, set_resource}, ++ }, ++ set_policy, ++}; ++use attestation_service::AttestationService; ++use clap::{arg, command, Parser}; + use env_logger; +-use actix_web::{web, App, HttpServer}; +-use std::{net::{SocketAddr, IpAddr, Ipv4Addr}, sync::Arc}; ++use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; ++use std::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); ++const DEFAULT_SOCKETADDR: &str = "localhost: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, ++ #[arg(short, long, default_value_t = DEFAULT_SOCKETADDR.to_string())] ++ socketaddr: String, + + /// Attestation Service config file + // Load `ASConfig` from a configuration file like: +@@ -47,6 +49,13 @@ struct Cli { + // } + #[arg(short, long, default_value_t = DEFAULT_ASCONFIG_FILE.to_string())] + config: String, ++ ++ #[arg(short = 'p', long = "protocol", default_value_t = String::from("http"))] ++ protocol: String, ++ #[arg(short = 't', long = "https_cert", default_value_t = String::from(""))] ++ https_cert: String, ++ #[arg(short = 'k', long = "https_key", default_value_t = String::from(""))] ++ https_key: String, + } + + #[actix_web::main] +@@ -54,14 +63,12 @@ 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 sessions_clone = session_map.clone(); ++ let server: AttestationService = AttestationService::new(None).unwrap(); ++ let sessions = server.get_sessions(); + tokio::spawn(async move { + loop { + tokio::time::sleep(std::time::Duration::from_secs(60)).await; +- sessions_clone ++ sessions + .session_map + .retain_async(|_, v| !v.is_expired()) + .await; +@@ -69,19 +76,36 @@ async fn main() -> Result<()> { + }); + + let service = web::Data::new(Arc::new(RwLock::new(server))); +- HttpServer::new(move || { ++ let http_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?; ++ .service(get_resource) ++ .service(set_resource) ++ .service(get_resource_policy) ++ .service(set_resource_policy) ++ }); ++ if cli.protocol == "https" { ++ if cli.https_cert.is_empty() || cli.https_key.is_empty() { ++ log::error!("cert or key is empty"); ++ return Ok(()); ++ } ++ let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls())?; ++ builder.set_private_key_file(cli.https_key, SslFiletype::PEM)?; ++ builder.set_certificate_chain_file(cli.https_cert)?; ++ http_server ++ .bind_openssl(cli.socketaddr, builder)? ++ .run() ++ .await?; ++ } else if cli.protocol == "http" { ++ http_server.bind(cli.socketaddr)?.run().await?; ++ } else { ++ log::error!("unknown protocol {}", cli.protocol); ++ } + + 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 +index d47698a..c3d6309 100644 +--- a/service/attestation/attestation-service/service/src/restapi/mod.rs ++++ b/service/attestation/attestation-service/service/src/restapi/mod.rs +@@ -1,43 +1,61 @@ + /* +- * 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 actix_web::{ post, get, web, HttpResponse, HttpRequest}; ++* 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 resource; ++ ++use crate::result::{AsError, Result}; ++use crate::session::Session; ++use crate::AttestationService; ++use actix_web::{get, post, web, HttpRequest, HttpResponse}; ++use attestation_types::SESSION_TIMEOUT_MIN; ++use log; + use serde::{Deserialize, Serialize}; + use std::sync::Arc; + use tokio::sync::RwLock; +-use log; +-use base64_url; +-use attestation_types::SESSION_TIMEOUT_MIN; +-use crate::session::{Session, SessionMap}; + + const DEFAULT_POLICY_DIR: &str = "/etc/attestation/attestation-service/policy"; + #[derive(Deserialize, Serialize, Debug)] +-pub struct ChallengeRequest {} ++pub struct ChallengeRequest { ++ pub user_data: Vec, ++} + + #[get("/challenge")] + pub async fn get_challenge( +- map: web::Data, ++ request: Option>, + service: web::Data>>, + ) -> Result { + log::debug!("challenge request"); ++ let user_data: Option>; ++ ++ if request.is_some() { ++ user_data = Some(request.unwrap().0.user_data); ++ if user_data.clone().unwrap().len() > 32 { ++ return Err(AsError::ParameterInvalid(String::from( ++ "user data length should not exceed 32", ++ ))); ++ } ++ log::debug!("user data is {:?}", user_data.clone().unwrap()); ++ } else { ++ log::debug!("user data is None"); ++ user_data = Option::None; ++ } ++ ++ let map = service.read().await.get_sessions(); ++ let challenge = service.read().await.generate_challenge(user_data).await; ++ let new_session = Session::new(challenge, SESSION_TIMEOUT_MIN); + +- let challenge = service.read().await.generate_challenge().await; +- let session = Session::new(challenge, SESSION_TIMEOUT_MIN); + let response = HttpResponse::Ok() +- .cookie(session.cookie()) +- .json(session.challenge.clone()); +- map.insert(session); ++ .cookie(new_session.cookie()) ++ .json(new_session.challenge.clone()); ++ map.insert(new_session); + + Ok(response) + } +@@ -52,42 +70,55 @@ pub struct AttestationRequest { + #[post("/attestation")] + pub async fn attestation( + http_req: HttpRequest, +- map: web::Data, + request: web::Json, + service: web::Data>>, + ) -> Result { + log::debug!("attestation request is coming"); ++ let map = service.read().await.get_sessions(); + let request = request.0; + let challenge = request.challenge; + + if http_req.headers().contains_key("as-challenge") { +- log::info!("sessions map len:{}", map.session_map.len()); +- 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::SessionNotFound)?; +- if session.is_expired() { +- return Err(Error::SessionExpired); +- } +- if challenge != session.challenge { +- log::error!("request challenge:{} does not match session challenge:{}", challenge, session.challenge); +- return Err(Error::ChallengeInvalid); +- } ++ log::warn!("attestation request lacks 'as-challenge' header field."); ++ } ++ ++ log::info!("sessions map len:{}", map.session_map.len()); ++ let cookie = http_req ++ .cookie("oeas-session-id") ++ .ok_or(AsError::CookieMissing)?; ++ let session = map ++ .session_map ++ .get_async(cookie.value()) ++ .await ++ .ok_or(AsError::SessionNotFound)?; ++ if session.is_expired() { ++ return Err(AsError::SessionExpired); ++ } ++ if challenge != session.challenge { ++ log::error!( ++ "request challenge:{} does not match session challenge:{}", ++ challenge, ++ session.challenge ++ ); ++ return Err(AsError::ChallengeInvalid); + } + +- let nonce = base64_url::decode(&challenge)?; ++ // The challenge in evidence is base64 encoded. ++ let nonce = challenge.as_bytes(); + let evidence = base64_url::decode(&request.evidence)?; + let ids = request.policy_id; +- let token = service.read().await.evaluate(&nonce, &evidence, &ids).await?; ++ let token = service ++ .read() ++ .await ++ .evaluate(&nonce, &evidence, &ids) ++ .await?; + +- Ok(HttpResponse::Ok().body(token)) ++ Ok(HttpResponse::Ok().cookie(cookie).body(token)) + } + + #[derive(Deserialize, Serialize, Debug)] + pub struct ReferenceRequest { +- refs: String ++ refs: String, + } + + #[post("/reference")] +@@ -97,7 +128,11 @@ pub async fn reference( + ) -> Result { + let request = request.0; + log::debug!("reference request: {:?}", request); +- service.read().await.register_reference(&request.refs).await?; ++ service ++ .read() ++ .await ++ .register_reference(&request.refs) ++ .await?; + Ok(HttpResponse::Ok().body("set reference success")) + } + +@@ -117,8 +152,12 @@ pub async fn set_policy( + 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); +- service.read().await.set_policy(&policy_id, &policy, &dir).await?; ++ let dir: String = String::from(DEFAULT_POLICY_DIR); ++ service ++ .read() ++ .await ++ .set_policy(&policy_id, &policy, &dir) ++ .await?; + Ok(HttpResponse::Ok().body("set policy success")) + } + +@@ -135,7 +174,11 @@ pub async fn get_policy( + let request = request.0; + log::debug!("get policy request: {:?}", request); + let id = request.policy_id.clone(); +- let dir:String = String::from(DEFAULT_POLICY_DIR); +- let ret = service.read().await.get_policy(&dir, &id.to_string()).await?; ++ let dir: String = String::from(DEFAULT_POLICY_DIR); ++ let ret = service ++ .read() ++ .await ++ .get_policy(&dir, &id.to_string()) ++ .await?; + Ok(HttpResponse::Ok().body(ret)) + } +diff --git a/service/attestation/attestation-service/service/src/restapi/resource/mod.rs b/service/attestation/attestation-service/service/src/restapi/resource/mod.rs +new file mode 100644 +index 0000000..4efa7e3 +--- /dev/null ++++ b/service/attestation/attestation-service/service/src/restapi/resource/mod.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. ++*/ ++ ++pub mod policy; ++pub mod storage; +diff --git a/service/attestation/attestation-service/service/src/restapi/resource/policy.rs b/service/attestation/attestation-service/service/src/restapi/resource/policy.rs +new file mode 100644 +index 0000000..77a63f1 +--- /dev/null ++++ b/service/attestation/attestation-service/service/src/restapi/resource/policy.rs +@@ -0,0 +1,116 @@ ++/* ++* 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::result::Result; ++use crate::AttestationService; ++use actix_web::{get, post, web, HttpResponse}; ++use attestation_types::resource::policy::PolicyLocation; ++use attestation_types::service::{GetResourcePolicyOp, SetResourcePolicyOp}; ++use serde::{Deserialize, Serialize}; ++use std::sync::Arc; ++use tokio::sync::RwLock; ++ ++#[get("/resource/policy")] ++pub async fn get_resource_policy( ++ body: web::Json, ++ agent: web::Data>>, ++) -> Result { ++ let agent = agent.read().await; ++ let admin = agent.resource_admin.lock().await; ++ let op = body.0; ++ match op { ++ GetResourcePolicyOp::GetOne { policy } => { ++ log::debug!("Request for getting policy {}", policy); ++ match admin.get_policy(policy.clone()).await { ++ Ok(content) => Ok(HttpResponse::Ok().body(content)), ++ Err(e) => { ++ log::warn!("Failed to get policy '{}'", policy); ++ Err(crate::result::AsError::from(e)) ++ } ++ } ++ } ++ GetResourcePolicyOp::GetAll => { ++ log::debug!("Request for getting all policies"); ++ match admin.get_all_policies().await { ++ Ok(policies) => { ++ let ret: Vec = policies ++ .iter() ++ .map(|location| String::from(location)) ++ .collect(); ++ let s = serde_json::to_string(&ret) ++ .unwrap_or(format!("Failed to serialize '{:?}'", ret)); ++ Ok(HttpResponse::Ok().body(s)) ++ } ++ Err(e) => { ++ log::warn!("Failed to get all policies"); ++ Err(crate::result::AsError::from(e)) ++ } ++ } ++ } ++ GetResourcePolicyOp::GetAllInVendor { vendor } => { ++ log::debug!("Request for getting all policies in vendor {}", vendor); ++ match admin.get_all_policies_in_vendor(&vendor).await { ++ Ok(policies) => { ++ let ret: Vec = policies ++ .iter() ++ .map(|location| String::from(location)) ++ .collect(); ++ let s = serde_json::to_string(&ret) ++ .unwrap_or(format!("Failed to serialize '{:?}'", ret)); ++ Ok(HttpResponse::Ok().body(s)) ++ } ++ Err(e) => { ++ log::warn!("Failed to get policies in vendor {}", vendor); ++ Err(crate::result::AsError::from(e)) ++ } ++ } ++ } ++ } ++} ++ ++#[post("/resource/policy")] ++pub async fn set_resource_policy( ++ body: web::Json, ++ agent: web::Data>>, ++) -> Result { ++ let agent = agent.read().await; ++ let admin = agent.resource_admin.lock().await; ++ let op = body.0; ++ ++ match op { ++ SetResourcePolicyOp::Add { policy, content } => { ++ admin ++ .add_policy(policy.clone(), &content) ++ .await ++ .map_err(|e| { ++ log::warn!("Failed to add policy {}: {}", policy, e); ++ e ++ })?; ++ } ++ SetResourcePolicyOp::Delete { policy } => { ++ admin.delete_policy(policy.clone()).await.map_err(|e| { ++ log::warn!("Failed to delete policy {}: {}", policy, e); ++ e ++ })?; ++ } ++ SetResourcePolicyOp::ClearAll { vendor } => { ++ admin ++ .clear_all_policies_in_vendor(&vendor) ++ .await ++ .map_err(|e| { ++ log::warn!("Failed to clear policies in vendor {}: {}", vendor, e); ++ e ++ })?; ++ } ++ } ++ ++ Ok(HttpResponse::Ok().body("successful")) ++} +diff --git a/service/attestation/attestation-service/service/src/restapi/resource/storage.rs b/service/attestation/attestation-service/service/src/restapi/resource/storage.rs +new file mode 100644 +index 0000000..7b90cda +--- /dev/null ++++ b/service/attestation/attestation-service/service/src/restapi/resource/storage.rs +@@ -0,0 +1,158 @@ ++/* ++* 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::result::{self, Result}; ++use crate::session::SessionMap; ++use crate::AttestationService; ++use actix_web::http::header::Header; ++use actix_web::web::Data; ++use actix_web::{get, post, web, HttpRequest, HttpResponse}; ++use actix_web_httpauth::headers::authorization::{Authorization, Bearer}; ++use anyhow::Context; ++use attestation_types::resource::ResourceLocation; ++use attestation_types::service::{GetResourceOp, SetResourceOp, SetResourceRequest}; ++use attestation_types::Claims; ++use log; ++use std::sync::Arc; ++use token_signer::verify; ++use tokio::sync::RwLock; ++ ++/// When the consumer request for resource, he should provide the vendor name which owns the resource. ++#[get("/resource/storage")] ++pub async fn get_resource( ++ req: HttpRequest, ++ body: web::Json, ++ agent: web::Data>>, ++) -> Result { ++ log::info!("receive getting resource request"); ++ ++ let sessions = agent.read().await.get_sessions(); ++ let op = body.0; ++ ++ match op { ++ GetResourceOp::TeeGet { resource } => { ++ tee_get_resource(req, sessions, agent, resource).await ++ } ++ GetResourceOp::VendorGet { vendor } => { ++ vendor_get_resource(req, sessions, agent, &vendor).await ++ } ++ } ++} ++ ++async fn tee_get_resource( ++ req: HttpRequest, ++ sessions: Data, ++ agent: web::Data>>, ++ resource: ResourceLocation, ++) -> Result { ++ log::info!("receive tee getting resource request"); ++ ++ // If the corresponding session of the token exists, get the token inside the session. ++ // Otherwise, get the token from the http header. ++ let token = match { ++ if let Some(cookie) = req.cookie("oeas-session-id") { ++ sessions ++ .session_map ++ .get_async(cookie.value()) ++ .await ++ .map(|session| session.get_token()) ++ .flatten() ++ .map(|t| { ++ log::debug!("Get token from session {}", cookie.value()); ++ t ++ }) ++ } else { ++ None ++ } ++ } { ++ Some(token) => token, ++ None => { ++ let bearer = Authorization::::parse(&req) ++ .context("failed to parse bearer token")? ++ .into_scheme(); ++ log::debug!("Get token from headers"); ++ bearer.token().to_string() ++ } ++ }; ++ ++ let claim: Claims = verify(&token).context("illegal token")?; ++ let claim: String = serde_json::to_string(&claim)?; ++ ++ log::debug!("Resource path: {}", resource); ++ log::debug!("Receive claim: {}", claim); ++ ++ match agent ++ .read() ++ .await ++ .resource_evaluate(resource.clone(), &claim) ++ .await ++ { ++ Ok(r) => { ++ if r { ++ log::debug!("Resource evaluate success."); ++ let content = agent.read().await.get_resource(resource).await?; ++ ++ Ok(HttpResponse::Ok().body(content)) ++ } else { ++ log::debug!("Resource evaluate fail."); ++ Ok(HttpResponse::BadRequest().body("resource evaluation failed")) ++ } ++ } ++ Err(e) => { ++ log::debug!("{}", e); ++ Err(result::AsError::Resource( ++ attestation_types::resource::error::ResourceError::LoadPolicy(e), ++ )) ++ } ++ } ++} ++ ++async fn vendor_get_resource( ++ _req: HttpRequest, ++ _sessions: Data, ++ agent: web::Data>>, ++ vendor: &str, ++) -> Result { ++ log::info!("receive vendor getting resource request"); ++ ++ let resource_list: Vec = agent ++ .read() ++ .await ++ .list_resource(vendor) ++ .await? ++ .iter() ++ .map(|v| v.to_string()) ++ .collect(); ++ ++ Ok(HttpResponse::Ok().body(serde_json::to_string(&resource_list)?)) ++} ++ ++#[post("/resource/storage")] ++pub async fn set_resource( ++ body: web::Json, ++ agent: web::Data>>, ++) -> Result { ++ log::info!("receive vendor setting resource request"); ++ ++ let agent = agent.read().await; ++ let admin = agent.resource_admin.lock().await; ++ let resource = body.0.resource.clone(); ++ match body.op.clone() { ++ SetResourceOp::Add { content, policy } => { ++ admin.add_resource(resource, content, policy).await? ++ } ++ SetResourceOp::Delete => admin.del_resource(resource).await?, ++ SetResourceOp::Modify { content } => admin.modify_resource(resource, content).await?, ++ SetResourceOp::Bind { policy } => admin.bind_policy(resource, policy).await?, ++ SetResourceOp::Unbind { policy } => admin.unbind_policy(resource, policy).await?, ++ } ++ Ok(HttpResponse::Ok().body("successful")) ++} +diff --git a/service/attestation/attestation-service/service/src/result/mod.rs b/service/attestation/attestation-service/service/src/result/mod.rs +index 7261d19..96b5a73 100644 +--- a/service/attestation/attestation-service/service/src/result/mod.rs ++++ b/service/attestation/attestation-service/service/src/result/mod.rs +@@ -9,14 +9,14 @@ + * 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; ++use thiserror::Error; ++pub type Result = std::result::Result; + + #[derive(Debug, Error)] + //#[non_exhaustive] + //#[allow(missing_docs)] +-pub enum Error { ++pub enum AsError { + #[error("IO error: {source:?}")] + Io { + #[from] +@@ -67,11 +67,20 @@ pub enum Error { + #[error("Request challenge is invalid")] + ChallengeInvalid, + ++ #[error("Request Prameter is invalid")] ++ ParameterInvalid(String), ++ ++ #[error("Illegal token")] ++ TokenIllegal, ++ ++ #[error("Resource Error: {0}")] ++ Resource(#[from] attestation_types::resource::error::ResourceError), ++ + #[error(transparent)] + Other(#[from] anyhow::Error), + } + +-impl ResponseError for Error { ++impl ResponseError for AsError { + 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 +index 2aee35a..be9f56e 100644 +--- a/service/attestation/attestation-service/service/src/session.rs ++++ b/service/attestation/attestation-service/service/src/session.rs +@@ -9,7 +9,10 @@ + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ +-use actix_web::cookie::{time::{Duration, OffsetDateTime}, Cookie}; ++use actix_web::cookie::{ ++ time::{Duration, OffsetDateTime}, ++ Cookie, ++}; + use scc::HashMap; + use uuid::Uuid; + +@@ -17,6 +20,13 @@ pub struct Session { + pub id: String, + pub challenge: String, + timeout: OffsetDateTime, ++ status: SessionStatus, ++} ++ ++enum SessionStatus { ++ Challenge, ++ // carry token ++ Attested(String), + } + + impl Session { +@@ -27,6 +37,7 @@ impl Session { + id, + challenge, + timeout, ++ status: SessionStatus::Challenge, + } + } + pub fn is_expired(&self) -> bool { +@@ -34,8 +45,21 @@ impl Session { + } + pub fn cookie(&self) -> Cookie { + Cookie::build("oeas-session-id", self.id.clone()) +- .expires(self.timeout.clone()) +- .finish() ++ .expires(self.timeout.clone()) ++ .finish() ++ } ++ ++ /// Update status of the session. ++ pub fn update(&mut self, token: String) { ++ self.status = SessionStatus::Attested(token); ++ } ++ ++ /// Get token if the session status is attested. ++ pub fn get_token(&self) -> Option { ++ match &self.status { ++ SessionStatus::Attested(t) => Some(t.clone()), ++ _ => None, ++ } + } + } + +@@ -52,4 +76,4 @@ impl SessionMap { + pub fn insert(&self, session: Session) { + let _ = self.session_map.insert(session.id.clone(), session); + } +-} +\ No newline at end of file ++} +diff --git a/service/attestation/attestation-service/tests/Cargo.toml b/service/attestation/attestation-service/tests/Cargo.toml +index 0fde476..70112b3 100644 +--- a/service/attestation/attestation-service/tests/Cargo.toml ++++ b/service/attestation/attestation-service/tests/Cargo.toml +@@ -5,5 +5,5 @@ edition = "2021" + + [dependencies] + serde_json = "1.0.116" +-reqwest = {version = "0.12.5", features = ["blocking"]} ++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 +index b8adb1e..79a96de 100644 +--- a/service/attestation/attestation-service/tests/src/lib.rs ++++ b/service/attestation/attestation-service/tests/src/lib.rs +@@ -161,12 +161,8 @@ mod tests { + 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(); ++ 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 +index 029008a..ac58c6c 100644 +--- a/service/attestation/attestation-service/token/Cargo.toml ++++ b/service/attestation/attestation-service/token/Cargo.toml +@@ -11,4 +11,4 @@ serde.workspace = true + serde_json.workspace = true + anyhow.workspace = true + attestation-types.workspace = true +-thiserror.workspace = true +\ No newline at end of file ++thiserror.workspace = true +diff --git a/service/attestation/attestation-service/token/src/lib.rs b/service/attestation/attestation-service/token/src/lib.rs +index 3ee785e..92f427f 100644 +--- a/service/attestation/attestation-service/token/src/lib.rs ++++ b/service/attestation/attestation-service/token/src/lib.rs +@@ -9,17 +9,18 @@ + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ +-use anyhow::{Result}; +-use jsonwebtoken::{encode, get_current_timestamp, +- Algorithm, EncodingKey, Header, ++use anyhow::Result; ++use attestation_types::{Claims, EvlResult}; ++use jsonwebtoken::{ ++ decode, decode_header, encode, get_current_timestamp, Algorithm, DecodingKey, EncodingKey, ++ Header, Validation, + }; +-use std::path::Path; + use serde::{Deserialize, Serialize}; + use serde_json::Value; +-use attestation_types::{EvlResult, Claims}; ++use std::path::Path; + use thiserror; + +-#[derive(thiserror::Error, Debug)] ++#[derive(thiserror::Error, Debug)] + pub enum SignError { + #[error("get unix time fail:{0:?}")] + ToUnixTimeFail(#[from] std::num::TryFromIntError), +@@ -30,7 +31,7 @@ pub enum SignError { + #[error("key content read fail:{0}")] + ReadKeyFail(String), + #[error("sign fail:{0:?}")] +- SignFail(#[from] jsonwebtoken::errors::Error) ++ SignFail(#[from] jsonwebtoken::errors::Error), + } + + #[derive(Debug, Clone, Serialize, Deserialize)] +@@ -54,7 +55,6 @@ impl Default for TokenSignConfig { + } + } + +- + #[derive(Debug, Clone, Serialize, Deserialize)] + pub struct EvlReport { + pub tee: String, +@@ -79,15 +79,13 @@ 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{ ++ 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 +- { ++ fn support_ps(alg: &Algorithm) -> bool { + if *alg == Algorithm::PS256 || *alg == Algorithm::PS384 || *alg == Algorithm::PS512 { + return true; + } +@@ -108,21 +106,45 @@ impl TokenSigner { + tcb_status: report.tcb_status.clone(), + }; + if !Self::support_rs(&alg) && !Self::support_ps(&alg) { +- return Err(SignError::UnsupportAlg(format!("unknown algrithm {:?}", alg))); ++ return Err(SignError::UnsupportAlg(format!( ++ "unknown algrithm {:?}", ++ alg ++ ))); + } + if !Path::new(&self.config.key).exists() { +- return Err(SignError::UnsupportAlg(format!("token verfify failed, {:?} cert not exist", self.config.key))); ++ return Err(SignError::UnsupportAlg(format!( ++ "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, +- _ => {return Err(SignError::ReadKeyFail(format!("get key from input error")));} ++ _ => { ++ return Err(SignError::ReadKeyFail(format!("get key from input error"))); ++ } + }; +- ++ + let token = match encode(&header, &claims, &key_value) { + Ok(val) => val, +- Err(e) => {return Err(SignError::SignFail(e));} ++ Err(e) => { ++ return Err(SignError::SignFail(e)); ++ } + }; + Ok(token) + } +-} +\ No newline at end of file ++} ++ ++pub fn verify(token: &String) -> Result { ++ let header = decode_header(&token)?; ++ let alg: Algorithm = header.alg; ++ ++ // todo: check support of verification algorithm ++ ++ let cert = std::fs::read("/etc/attestation/attestation-service/token/as_cert.pem").unwrap(); ++ let key_value = DecodingKey::from_rsa_pem(&cert)?; ++ let validation = Validation::new(alg); ++ ++ let data = decode::(&token, &key_value, &validation)?; ++ Ok(data.claims) ++} +diff --git a/service/attestation/attestation-service/verifier/Cargo.toml b/service/attestation/attestation-service/verifier/Cargo.toml +index e870fa7..baab873 100644 +--- a/service/attestation/attestation-service/verifier/Cargo.toml ++++ b/service/attestation/attestation-service/verifier/Cargo.toml +@@ -17,11 +17,15 @@ ima-measurements.workspace = true + rand.workspace = true + fallible-iterator.workspace = true + attestation-types.workspace = true ++ccatoken.workspace = true ++ear.workspace = true ++base64-url.workspace = true + + [dev-dependencies] + + [features] +-default = [ "itrustee-verifier","virtcca-verifier" ] ++default = ["itrustee-verifier", "virtcca-verifier"] + itrustee-verifier = [] + virtcca-verifier = [] ++rustcca-verifier = [] + no_as = [] +diff --git a/service/attestation/attestation-service/verifier/src/itrustee/mod.rs b/service/attestation/attestation-service/verifier/src/itrustee/mod.rs +index 8ce4d24..029f751 100644 +--- a/service/attestation/attestation-service/verifier/src/itrustee/mod.rs ++++ b/service/attestation/attestation-service/verifier/src/itrustee/mod.rs +@@ -15,12 +15,13 @@ + use super::*; + use log; + use serde_json::json; +-use std::path::Path; + use std::ops::Add; ++use std::path::Path; + + mod itrustee; + +-const ITRUSTEE_REF_VALUE_FILE: &str = "/etc/attestation/attestation-service/verifier/itrustee/basevalue.txt"; ++const ITRUSTEE_REF_VALUE_FILE: &str = ++ "/etc/attestation/attestation-service/verifier/itrustee/basevalue.txt"; + + #[derive(Debug, Default)] + pub struct ItrusteeVerifier {} +@@ -30,9 +31,23 @@ impl ItrusteeVerifier { + return evalute_wrapper(user_data, evidence); + } + } +- ++const MAX_CHALLENGE_LEN: usize = 64; + fn evalute_wrapper(user_data: &[u8], evidence: &[u8]) -> Result { +- let mut in_data = user_data.to_vec(); ++ let challenge = base64_url::decode(user_data)?; ++ let len = 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 ++ ); ++ } ++ let mut in_data = challenge.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, +@@ -43,10 +58,16 @@ fn evalute_wrapper(user_data: &[u8], evidence: &[u8]) -> Result { + buf: in_data.as_mut_ptr() as *mut ::std::os::raw::c_uchar, + }; + +- let policy: std::os::raw::c_int = 1; // 1: verify ta_imag; 2: verfiy ta_mem; 3: verify ta_img and ta_mem hash; ++ let policy: std::os::raw::c_int = 1; // 1: verify ta_imag; 2: verfiy ta_mem; 3: verify ta_img and ta_mem hash; + 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); ++ 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"); +diff --git a/service/attestation/attestation-service/verifier/src/lib.rs b/service/attestation/attestation-service/verifier/src/lib.rs +index 0b776c2..43894db 100644 +--- a/service/attestation/attestation-service/verifier/src/lib.rs ++++ b/service/attestation/attestation-service/verifier/src/lib.rs +@@ -11,12 +11,12 @@ + */ + + //! Unified tee verifier +-//! ++//! + //! This crate provides unified APIs to verify TEE evidence. + + use anyhow::*; +-use serde_json; + use async_trait::async_trait; ++use serde_json; + + use attestation_types::{Evidence, TeeType}; + +@@ -26,6 +26,9 @@ pub mod itrustee; + #[cfg(feature = "virtcca-verifier")] + pub mod virtcca; + ++#[cfg(feature = "rustcca-verifier")] ++pub mod rustcca; ++ + pub type TeeClaim = serde_json::Value; + + #[derive(Debug, Default)] +@@ -36,25 +39,41 @@ pub trait VerifierAPIs { + async fn verify_evidence(&self, user_data: &[u8], evidence: &[u8]) -> 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, ++ TeeType::Itrustee => { ++ itrustee::ItrusteeVerifier::default() ++ .evaluate(user_data, evidence) ++ .await ++ } + #[cfg(feature = "virtcca-verifier")] +- TeeType::Virtcca => virtcca::VirtCCAVerifier::default().evaluate(user_data, evidence).await, ++ TeeType::Virtcca => { ++ virtcca::VirtCCAVerifier::default() ++ .evaluate(user_data, evidence) ++ .await ++ } ++ #[cfg(feature = "rustcca-verifier")] ++ TeeType::Rustcca => { ++ rustcca::RustCCAVerifier::default() ++ .evaluate(user_data, evidence) ++ .await ++ } + _ => bail!("unsupported tee type:{:?}", tee_type), + } + } + } ++ ++#[cfg(feature = "no_as")] ++pub fn virtcca_parse_evidence(evidence: &[u8]) -> Result { ++ let aa_evidence: Evidence = serde_json::from_slice(evidence)?; ++ let evidence = aa_evidence.evidence.as_bytes(); ++ ++ return virtcca::Evidence::parse_evidence(evidence); ++} +diff --git a/service/attestation/attestation-service/verifier/src/rustcca/LICENSE b/service/attestation/attestation-service/verifier/src/rustcca/LICENSE +new file mode 100644 +index 0000000..2815c82 +--- /dev/null ++++ b/service/attestation/attestation-service/verifier/src/rustcca/LICENSE +@@ -0,0 +1,201 @@ ++ Apache License ++ Version 2.0, January 2004 ++ http://www.apache.org/licenses/ ++ ++ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION ++ ++ 1. Definitions. ++ ++ "License" shall mean the terms and conditions for use, reproduction, ++ and distribution as defined by Sections 1 through 9 of this document. ++ ++ "Licensor" shall mean the copyright owner or entity authorized by ++ the copyright owner that is granting the License. ++ ++ "Legal Entity" shall mean the union of the acting entity and all ++ other entities that control, are controlled by, or are under common ++ control with that entity. For the purposes of this definition, ++ "control" means (i) the power, direct or indirect, to cause the ++ direction or management of such entity, whether by contract or ++ otherwise, or (ii) ownership of fifty percent (50%) or more of the ++ outstanding shares, or (iii) beneficial ownership of such entity. ++ ++ "You" (or "Your") shall mean an individual or Legal Entity ++ exercising permissions granted by this License. ++ ++ "Source" form shall mean the preferred form for making modifications, ++ including but not limited to software source code, documentation ++ source, and configuration files. ++ ++ "Object" form shall mean any form resulting from mechanical ++ transformation or translation of a Source form, including but ++ not limited to compiled object code, generated documentation, ++ and conversions to other media types. ++ ++ "Work" shall mean the work of authorship, whether in Source or ++ Object form, made available under the License, as indicated by a ++ copyright notice that is included in or attached to the work ++ (an example is provided in the Appendix below). ++ ++ "Derivative Works" shall mean any work, whether in Source or Object ++ form, that is based on (or derived from) the Work and for which the ++ editorial revisions, annotations, elaborations, or other modifications ++ represent, as a whole, an original work of authorship. For the purposes ++ of this License, Derivative Works shall not include works that remain ++ separable from, or merely link (or bind by name) to the interfaces of, ++ the Work and Derivative Works thereof. ++ ++ "Contribution" shall mean any work of authorship, including ++ the original version of the Work and any modifications or additions ++ to that Work or Derivative Works thereof, that is intentionally ++ submitted to Licensor for inclusion in the Work by the copyright owner ++ or by an individual or Legal Entity authorized to submit on behalf of ++ the copyright owner. For the purposes of this definition, "submitted" ++ means any form of electronic, verbal, or written communication sent ++ to the Licensor or its representatives, including but not limited to ++ communication on electronic mailing lists, source code control systems, ++ and issue tracking systems that are managed by, or on behalf of, the ++ Licensor for the purpose of discussing and improving the Work, but ++ excluding communication that is conspicuously marked or otherwise ++ designated in writing by the copyright owner as "Not a Contribution." ++ ++ "Contributor" shall mean Licensor and any individual or Legal Entity ++ on behalf of whom a Contribution has been received by Licensor and ++ subsequently incorporated within the Work. ++ ++ 2. Grant of Copyright License. Subject to the terms and conditions of ++ this License, each Contributor hereby grants to You a perpetual, ++ worldwide, non-exclusive, no-charge, royalty-free, irrevocable ++ copyright license to reproduce, prepare Derivative Works of, ++ publicly display, publicly perform, sublicense, and distribute the ++ Work and such Derivative Works in Source or Object form. ++ ++ 3. Grant of Patent License. Subject to the terms and conditions of ++ this License, each Contributor hereby grants to You a perpetual, ++ worldwide, non-exclusive, no-charge, royalty-free, irrevocable ++ (except as stated in this section) patent license to make, have made, ++ use, offer to sell, sell, import, and otherwise transfer the Work, ++ where such license applies only to those patent claims licensable ++ by such Contributor that are necessarily infringed by their ++ Contribution(s) alone or by combination of their Contribution(s) ++ with the Work to which such Contribution(s) was submitted. If You ++ institute patent litigation against any entity (including a ++ cross-claim or counterclaim in a lawsuit) alleging that the Work ++ or a Contribution incorporated within the Work constitutes direct ++ or contributory patent infringement, then any patent licenses ++ granted to You under this License for that Work shall terminate ++ as of the date such litigation is filed. ++ ++ 4. Redistribution. You may reproduce and distribute copies of the ++ Work or Derivative Works thereof in any medium, with or without ++ modifications, and in Source or Object form, provided that You ++ meet the following conditions: ++ ++ (a) You must give any other recipients of the Work or ++ Derivative Works a copy of this License; and ++ ++ (b) You must cause any modified files to carry prominent notices ++ stating that You changed the files; and ++ ++ (c) You must retain, in the Source form of any Derivative Works ++ that You distribute, all copyright, patent, trademark, and ++ attribution notices from the Source form of the Work, ++ excluding those notices that do not pertain to any part of ++ the Derivative Works; and ++ ++ (d) If the Work includes a "NOTICE" text file as part of its ++ distribution, then any Derivative Works that You distribute must ++ include a readable copy of the attribution notices contained ++ within such NOTICE file, excluding those notices that do not ++ pertain to any part of the Derivative Works, in at least one ++ of the following places: within a NOTICE text file distributed ++ as part of the Derivative Works; within the Source form or ++ documentation, if provided along with the Derivative Works; or, ++ within a display generated by the Derivative Works, if and ++ wherever such third-party notices normally appear. The contents ++ of the NOTICE file are for informational purposes only and ++ do not modify the License. You may add Your own attribution ++ notices within Derivative Works that You distribute, alongside ++ or as an addendum to the NOTICE text from the Work, provided ++ that such additional attribution notices cannot be construed ++ as modifying the License. ++ ++ You may add Your own copyright statement to Your modifications and ++ may provide additional or different license terms and conditions ++ for use, reproduction, or distribution of Your modifications, or ++ for any such Derivative Works as a whole, provided Your use, ++ reproduction, and distribution of the Work otherwise complies with ++ the conditions stated in this License. ++ ++ 5. Submission of Contributions. Unless You explicitly state otherwise, ++ any Contribution intentionally submitted for inclusion in the Work ++ by You to the Licensor shall be under the terms and conditions of ++ this License, without any additional terms or conditions. ++ Notwithstanding the above, nothing herein shall supersede or modify ++ the terms of any separate license agreement you may have executed ++ with Licensor regarding such Contributions. ++ ++ 6. Trademarks. This License does not grant permission to use the trade ++ names, trademarks, service marks, or product names of the Licensor, ++ except as required for reasonable and customary use in describing the ++ origin of the Work and reproducing the content of the NOTICE file. ++ ++ 7. Disclaimer of Warranty. Unless required by applicable law or ++ agreed to in writing, Licensor provides the Work (and each ++ Contributor provides its Contributions) on an "AS IS" BASIS, ++ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or ++ implied, including, without limitation, any warranties or conditions ++ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A ++ PARTICULAR PURPOSE. You are solely responsible for determining the ++ appropriateness of using or redistributing the Work and assume any ++ risks associated with Your exercise of permissions under this License. ++ ++ 8. Limitation of Liability. In no event and under no legal theory, ++ whether in tort (including negligence), contract, or otherwise, ++ unless required by applicable law (such as deliberate and grossly ++ negligent acts) or agreed to in writing, shall any Contributor be ++ liable to You for damages, including any direct, indirect, special, ++ incidental, or consequential damages of any character arising as a ++ result of this License or out of the use or inability to use the ++ Work (including but not limited to damages for loss of goodwill, ++ work stoppage, computer failure or malfunction, or any and all ++ other commercial damages or losses), even if such Contributor ++ has been advised of the possibility of such damages. ++ ++ 9. Accepting Warranty or Additional Liability. While redistributing ++ the Work or Derivative Works thereof, You may choose to offer, ++ and charge a fee for, acceptance of support, warranty, indemnity, ++ or other liability obligations and/or rights consistent with this ++ License. However, in accepting such obligations, You may act only ++ on Your own behalf and on Your sole responsibility, not on behalf ++ of any other Contributor, and only if You agree to indemnify, ++ defend, and hold each Contributor harmless for any liability ++ incurred by, or claims asserted against, such Contributor by reason ++ of your accepting any such warranty or additional liability. ++ ++ END OF TERMS AND CONDITIONS ++ ++ APPENDIX: How to apply the Apache License to your work. ++ ++ To apply the Apache License to your work, attach the following ++ boilerplate notice, with the fields enclosed by brackets "[]" ++ replaced with your own identifying information. (Don't include ++ the brackets!) The text should be enclosed in the appropriate ++ comment syntax for the file format. We also recommend that a ++ file or class name and description of purpose be included on the ++ same "printed page" as the copyright notice for easier ++ identification within third-party archives. ++ ++ Copyright 2023 Contributors to the Veraison project. ++ ++ Licensed under the Apache License, Version 2.0 (the "License"); ++ you may not use this file except in compliance with the License. ++ You may obtain a copy of the License at ++ ++ http://www.apache.org/licenses/LICENSE-2.0 ++ ++ Unless required by applicable law or agreed to in writing, software ++ distributed under the License is distributed on an "AS IS" BASIS, ++ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ See the License for the specific language governing permissions and ++ limitations under the License. +diff --git a/service/attestation/attestation-service/verifier/src/rustcca/mod.rs b/service/attestation/attestation-service/verifier/src/rustcca/mod.rs +new file mode 100644 +index 0000000..bd2da4c +--- /dev/null ++++ b/service/attestation/attestation-service/verifier/src/rustcca/mod.rs +@@ -0,0 +1,246 @@ ++// Copyright 2023 Contributors to the Veraison project. ++// SPDX-License-Identifier: Apache-2.0 ++ ++/* ++ * 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. ++ */ ++ ++//! rust-cca verifier plugin ++use super::TeeClaim; ++use anyhow::{bail, Result}; ++use ear::claim::*; ++use serde_json::json; ++extern crate ccatoken; ++use ccatoken::store::{ ++ Cpak, MemoRefValueStore, MemoTrustAnchorStore, PlatformRefValue, RealmRefValue, RefValues, ++ SwComponent, ++}; ++use ccatoken::token; ++ ++use serde_json::value::RawValue; ++use std::error::Error; ++ ++const TEST_CPAK: &str = include_str!("../../test_data/cpak.json"); ++ ++#[derive(Debug, Default)] ++pub struct RustCCAVerifier {} ++ ++impl RustCCAVerifier { ++ pub async fn evaluate(&self, user_data: &[u8], evidence: &[u8]) -> Result { ++ return evalute_wrapper(user_data, evidence); ++ } ++} ++ ++//参数是challenge 和report ++// 1. execute golden to get tas, rvs ++// 2. execute verify ++fn evalute_wrapper(user_data: &[u8], evidence: &[u8]) -> Result { ++ let mut in_evidence = ++ token::Evidence::decode(&evidence.to_vec()).unwrap_or_else(|_| panic!("decode evidence")); ++ ++ let cpak = map_str_to_cpak(&in_evidence.platform_claims, &TEST_CPAK) ++ .unwrap_or_else(|_| panic!("map cpak")); ++ let _ = in_evidence ++ .verify_with_cpak(cpak) ++ .unwrap_or_else(|_| panic!("verify cpak")); ++ ++ let (platform_tvec, realm_tvec) = in_evidence.get_trust_vectors(); ++ if platform_tvec.instance_identity != TRUSTWORTHY_INSTANCE { ++ bail!("platform is not trustworthy"); ++ } ++ if realm_tvec.instance_identity != TRUSTWORTHY_INSTANCE { ++ bail!("realm is not trustworthy"); ++ } ++ ++ let rv = map_evidence_to_refval(&in_evidence).unwrap_or_else(|_| panic!("map refval")); ++ let ta = map_evidence_to_trustanchor(&in_evidence.platform_claims, &TEST_CPAK) ++ .unwrap_or_else(|_| panic!("map trustanchor")); ++ ++ let mut rvs: MemoRefValueStore = Default::default(); ++ rvs.load_json(&rv).unwrap_or_else(|_| panic!("load rvs")); ++ let mut tas: MemoTrustAnchorStore = Default::default(); ++ tas.load_json(&ta).unwrap_or_else(|_| panic!("load tas")); ++ let _ = in_evidence.verify(&tas); ++ ++ //verify challenge ++ let _ = verify_realm_challenge(user_data, &in_evidence.realm_claims); ++ ++ let payload = json!({ ++ "platform trust vector": serde_json::to_string_pretty(&platform_tvec).unwrap(), ++ "realm trust vector" : serde_json::to_string_pretty(&realm_tvec).unwrap(), ++ "realm" : { ++ "challenge" : hex::encode(in_evidence.realm_claims.challenge.clone()), ++ "perso" : hex::encode(in_evidence.realm_claims.perso.clone()), ++ "hash_alg" : hex::encode(in_evidence.realm_claims.hash_alg.clone()), ++ "rak" : hex::encode(in_evidence.realm_claims.rak.clone()) ++ } ++ }); ++ ++ let claim = json!({ ++ "tee_type": "ccatoken", ++ "payload" : payload, ++ }); ++ Ok(claim as TeeClaim) ++} ++ ++fn verify_realm_challenge(challenge: &[u8], realm_token: &token::Realm) -> Result<()> { ++ let len = challenge.len(); ++ let token_challenge = &realm_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 ++ ); ++ } ++ ++ Ok(()) ++} ++fn map_str_to_cpak(p: &token::Platform, cpak_str: &str) -> Result> { ++ let raw_pkey = RawValue::from_string(cpak_str.to_string())?; ++ ++ let mut v = Cpak { ++ raw_pkey, ++ inst_id: p.inst_id, ++ impl_id: p.impl_id, ++ ..Default::default() ++ }; ++ v.parse_pkey()?; ++ Ok(v) ++} ++ ++fn map_evidence_to_refval(e: &token::Evidence) -> Result> { ++ let prv = map_evidence_to_platform_refval(&e.platform_claims)?; ++ let rrv = map_evidence_to_realm_refval(&e.realm_claims)?; ++ ++ let rvs: RefValues = RefValues { ++ platform: Some(vec![prv]), ++ realm: Some(vec![rrv]), ++ }; ++ ++ let j = serde_json::to_string_pretty(&rvs)?; ++ ++ Ok(j) ++} ++ ++fn map_evidence_to_platform_refval( ++ p: &token::Platform, ++) -> Result> { ++ let mut v = PlatformRefValue { ++ impl_id: p.impl_id, ++ config: p.config.clone(), ++ ..Default::default() ++ }; ++ ++ for other in &p.sw_components { ++ let swc = SwComponent { ++ mval: other.mval.clone(), ++ signer_id: other.signer_id.clone(), ++ version: other.version.clone(), ++ mtyp: other.mtyp.clone(), ++ }; ++ ++ v.sw_components.push(swc) ++ } ++ ++ Ok(v) ++} ++ ++fn map_evidence_to_realm_refval(p: &token::Realm) -> Result> { ++ let mut v = RealmRefValue { ++ perso: p.perso.to_vec(), ++ rim: p.rim.clone(), ++ rak_hash_alg: p.rak_hash_alg.clone(), ++ ..Default::default() ++ }; ++ ++ for (i, other) in p.rem.iter().enumerate() { ++ v.rem[i].value.clone_from(other); ++ } ++ ++ Ok(v) ++} ++ ++fn map_evidence_to_trustanchor(p: &token::Platform, cpak: &str) -> Result> { ++ let raw_pkey = RawValue::from_string(cpak.to_string())?; ++ ++ let v = Cpak { ++ raw_pkey, ++ inst_id: p.inst_id, ++ impl_id: p.impl_id, ++ ..Default::default() // pkey is not serialised ++ }; ++ ++ let j = serde_json::to_string_pretty(&vec![v])?; ++ ++ Ok(j) ++} ++#[cfg(test)] ++mod tests { ++ use super::*; ++ use ear::claim::TRUSTWORTHY_INSTANCE; ++ ++ const TEST_CCA_TOKEN: &[u8; 1222] = include_bytes!("../../test_data/cca-token-01.cbor"); ++ //const TEST_CCA_TOKEN: &[u8; 1125] = include_bytes!("../../test_data/cca-token-02.cbor"); ++ const TEST_CPAK: &str = include_str!("../../test_data/cpak.json"); ++ ++ #[test] ++ fn cca_test() -> Result<(), Box> { ++ let mut evidence = ++ token::Evidence::decode(&TEST_CCA_TOKEN.to_vec()).expect("decoding TEST_CCA_TOKEN"); ++ ++ let j = TEST_CPAK; ++ let cpak = map_str_to_cpak(&evidence.platform_claims, &j)?; ++ let _ = evidence.verify_with_cpak(cpak)?; ++ ++ let (platform_tvec, realm_tvec) = evidence.get_trust_vectors(); ++ if platform_tvec.instance_identity != TRUSTWORTHY_INSTANCE { ++ return Err("platform is not trustworthy".into()); ++ } ++ if realm_tvec.instance_identity != TRUSTWORTHY_INSTANCE { ++ return Err("realm is not trustworthy".into()); ++ } ++ ++ let rv = map_evidence_to_refval(&evidence)?; ++ let ta = map_evidence_to_trustanchor(&evidence.platform_claims, &j)?; ++ ++ let mut rvs: MemoRefValueStore = Default::default(); ++ rvs.load_json(&rv)?; ++ let mut tas: MemoTrustAnchorStore = Default::default(); ++ tas.load_json(&ta)?; ++ let _ = evidence.verify(&tas); ++ ++ let (platform_tvec, realm_tvec) = evidence.get_trust_vectors(); ++ let payload = json!({ ++ "platform trust vector": serde_json::to_string_pretty(&platform_tvec).unwrap(), ++ "realm trust vector" : serde_json::to_string_pretty(&realm_tvec).unwrap(), ++ "realm" : { ++ "challenge" : hex::encode(evidence.realm_claims.challenge.clone()), ++ "perso" : hex::encode(evidence.realm_claims.perso.clone()), ++ "hash_alg" : hex::encode(evidence.realm_claims.hash_alg.clone()), ++ "rak" : hex::encode(evidence.realm_claims.rak.clone()) ++ } ++ }); ++ ++ let claim = json!({ ++ "tee_type": "ccatoken", ++ "payload" : payload, ++ }); ++ println!("verify success {:?}", claim); ++ Ok(()) ++ } ++} +diff --git a/service/attestation/attestation-service/verifier/src/virtcca/ima.rs b/service/attestation/attestation-service/verifier/src/virtcca/ima.rs +index 30a151f..220a52d 100644 +--- a/service/attestation/attestation-service/verifier/src/virtcca/ima.rs ++++ b/service/attestation/attestation-service/verifier/src/virtcca/ima.rs +@@ -9,12 +9,19 @@ + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ +-use anyhow::{Result, bail}; +-use ima_measurements::{Event, EventData, Parser}; ++use anyhow::{bail, Result}; + use fallible_iterator::FallibleIterator; +-use serde_json::{Value, Map, json}; ++use ima_measurements::{Event, EventData, Parser}; ++use serde_json::{json, Map, Value}; ++ ++#[cfg(not(feature = "no_as"))] ++const IMA_REFERENCE_FILE: &str = ++ "/etc/attestation/attestation-service/verifier/virtcca/ima/digest_list_file"; + +-const IMA_REFERENCE_FILE: &str = "/etc/attestation/attestation-service/verifier/virtcca/ima/digest_list_file"; ++// attestation agent local ima reference ++#[cfg(feature = "no_as")] ++const IMA_REFERENCE_FILE: &str = ++ "/etc/attestation/attestation-agent/local_verifier/virtcca/ima/digest_list_file"; + + #[derive(Debug, Default)] + pub struct ImaVerify {} +@@ -35,10 +42,13 @@ impl ImaVerify { + let pcr_10 = pcr_values.get(&10).expect("PCR 10 not measured"); + let string_pcr_sha256 = hex::encode(pcr_10.sha256); + let string_ima_log_hash = hex::encode(ima_log_hash); +- ++ + if string_pcr_sha256.clone() != string_ima_log_hash { +- log::error!("ima log verify failed string_pcr_sha256 {}, string_ima_log_hash {}", +- string_pcr_sha256, string_ima_log_hash); ++ log::error!( ++ "ima log verify failed string_pcr_sha256 {}, string_ima_log_hash {}", ++ string_pcr_sha256, ++ string_ima_log_hash ++ ); + bail!("ima log hash verify failed"); + } + +@@ -50,15 +60,21 @@ impl ImaVerify { + let mut ima_detail = Map::new(); + // parser each file digest in ima log, and compare with reference base value + for event in events { +- let (name ,file_digest) = match event.data { +- EventData::ImaNg{digest, name} => (name, digest.digest), +- _ => bail!("Inalid event {:?}", event), ++ let (name, file_digest) = match event.data { ++ EventData::ImaNg { digest, name } => (name, digest.digest), ++ _ => bail!("Invalid event {:?}", event), + }; ++ if name == "boot_aggregate".to_string() { ++ continue; ++ } + let hex_str_digest = hex::encode(file_digest); + if ima_refs.contains(&hex_str_digest) { + ima_detail.insert(name, Value::Bool(true)); + } else { +- log::error!("there is no refernce base value of file digest {:?}", hex_str_digest); ++ log::error!( ++ "there is no refernce base value of file digest {:?}", ++ hex_str_digest ++ ); + ima_detail.insert(name, Value::Bool(false)); + } + } +@@ -72,14 +88,16 @@ impl ImaVerify { + use std::io::BufRead; + use std::io::BufReader; + fn file_reader(file_path: &str) -> ::std::io::Result> { +- let file = std::fs::File::open(file_path)?; ++ let file = std::fs::File::open(file_path).expect("open ima reference file failed"); + let mut strings = Vec::::new(); + let mut reader = BufReader::new(file); + let mut buf = String::new(); + let mut n: usize; + loop { + n = reader.read_line(&mut buf)?; +- if n == 0 { break; } ++ if n == 0 { ++ break; ++ } + buf.pop(); + strings.push(buf.clone()); + buf.clear(); +diff --git a/service/attestation/attestation-service/verifier/src/virtcca/mod.rs b/service/attestation/attestation-service/verifier/src/virtcca/mod.rs +index 3de7c9f..ff72f77 100644 +--- a/service/attestation/attestation-service/verifier/src/virtcca/mod.rs ++++ b/service/attestation/attestation-service/verifier/src/virtcca/mod.rs +@@ -13,40 +13,60 @@ + //! virtcca verifier plugin + use super::TeeClaim; + +-use anyhow::{Result, bail, anyhow}; +-use cose::keys::CoseKey; +-use cose::message::CoseMessage; ++use anyhow::{anyhow, bail, Result}; + use ciborium; + use ciborium::Value; +-use openssl::rsa; ++use cose::keys::CoseKey; ++use cose::message::CoseMessage; ++use log; ++use openssl::pkey::PKey; + use openssl::pkey::Public; ++use openssl::rsa; + 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"; ++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"; ++const VIRTCCA_SUB_CERT: &str = ++ "/etc/attestation/attestation-service/verifier/virtcca/Huawei IT Product CA.pem"; + +-// attestation agent local reference ++// attestation agent local reference + #[cfg(feature = "no_as")] +-const VIRTCCA_REF_VALUE_FILE: &str = "/etc/attestation/attestation-agent/local_verifier/virtcca/ref_value.json"; ++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"; ++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"; ++const VIRTCCA_SUB_CERT: &str = ++ "/etc/attestation/attestation-agent/local_verifier/virtcca/Huawei IT Product CA.pem"; + + #[derive(Debug, Default)] + pub struct VirtCCAVerifier {} + ++const MAX_CHALLENGE_LEN: usize = 64; + impl VirtCCAVerifier { + pub async fn evaluate(&self, user_data: &[u8], evidence: &[u8]) -> Result { +- return Evidence::verify(user_data, evidence); ++ let challenge = base64_url::decode(user_data)?; ++ let len = 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 ++ ); ++ } ++ return Evidence::verify(&challenge.to_vec(), evidence); + } + } + +@@ -68,13 +88,13 @@ 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 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 { +@@ -106,14 +126,27 @@ impl Evidence { + // verify ima + let ima_log = match virtcca_ev.ima_log { + Some(ima_log) => ima_log, +- _ => {log::info!("no ima log"); vec![]}, ++ _ => { ++ log::info!("no ima log"); ++ vec![] ++ } + }; +- let ima: serde_json::Value = ima::ImaVerify::default() +- .ima_verify(&ima_log, evidence.cvm_token.rem[0].clone())?; ++ let ima: serde_json::Value = ++ ima::ImaVerify::default().ima_verify(&ima_log, evidence.cvm_token.rem[0].clone())?; + + // todo parsed TeeClaim + evidence.parse_claim_from_evidence(ima) + } ++ pub fn parse_evidence(evidence: &[u8]) -> Result { ++ let virtcca_ev: VirtccaEvidence = serde_json::from_slice(evidence)?; ++ let evidence = virtcca_ev.evidence; ++ let evidence = Evidence::decode(evidence)?; ++ ++ let ima = json!(""); ++ // parsed TeeClaim ++ let claim = evidence.parse_claim_from_evidence(ima).unwrap(); ++ Ok(claim["payload"].clone() as TeeClaim) ++ } + fn parse_claim_from_evidence(&self, ima: serde_json::Value) -> Result { + let payload = json!({ + "vcca.cvm.challenge": hex::encode(self.cvm_token.challenge.clone()), +@@ -173,10 +206,16 @@ impl Evidence { + 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); ++ 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 +@@ -189,8 +228,12 @@ impl Evidence { + 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:?}"))?; ++ 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 +@@ -238,11 +281,22 @@ impl Evidence { + + // 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); ++ 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); ++ 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() { +@@ -268,7 +322,7 @@ impl Evidence { + Err(e) => { + log::error!("decode COSE failed, {:?}", e); + bail!("decode COSE failed"); +- }, ++ } + } + + // decode cvm CBOR payload +@@ -315,7 +369,7 @@ impl CvmToken { + 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), ++ err => bail!("cvm payload unknown label {}", err), + } + } else { + bail!("cvm payload expecting integer key"); +@@ -334,7 +388,11 @@ impl CvmToken { + } + let tmp = tmp.unwrap().clone(); + if tmp.len() != CVM_CHALLENGE_SIZE { +- bail!("cvm token challenge expecting {} bytes, got {}", CVM_CHALLENGE_SIZE,tmp.len()); ++ bail!( ++ "cvm token challenge expecting {} bytes, got {}", ++ CVM_CHALLENGE_SIZE, ++ tmp.len() ++ ); + } + self.challenge[..].clone_from_slice(&tmp); + Ok(()) +@@ -346,7 +404,11 @@ impl CvmToken { + } + let tmp = tmp.unwrap().clone(); + if tmp.len() != CVM_RPV_SIZE { +- bail!("cvm token rpv expecting {} bytes, got {}", CVM_RPV_SIZE, tmp.len()); ++ bail!( ++ "cvm token rpv expecting {} bytes, got {}", ++ CVM_RPV_SIZE, ++ tmp.len() ++ ); + } + self.rpv[..].clone_from_slice(&tmp); + Ok(()) +@@ -358,7 +420,11 @@ impl CvmToken { + } + 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()); ++ bail!( ++ "cvm token {} expecting 32, 48 or 64 bytes, got {}", ++ who, ++ tmp.len() ++ ); + } + Ok(tmp) + } +@@ -373,7 +439,11 @@ impl CvmToken { + } + let tmp = tmp.unwrap().clone(); + if tmp.len() != 4 { +- bail!("cvm token rem expecting size {}, got {}", CVM_REM_ARR_SIZE, tmp.len()); ++ bail!( ++ "cvm token rem expecting size {}, got {}", ++ CVM_REM_ARR_SIZE, ++ tmp.len() ++ ); + } + + for (i, val) in tmp.iter().enumerate() { +@@ -399,7 +469,11 @@ impl CvmToken { + } + 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()); ++ bail!( ++ "cvm token pub key len expecting {}, got {}", ++ CVM_PUB_KEY_SIZE, ++ tmp.len() ++ ); + } + self.pub_key[..].clone_from_slice(&tmp); + Ok(()) +diff --git a/service/attestation/attestation-types/Cargo.toml b/service/attestation/attestation-types/Cargo.toml +index 1fcf465..d50b822 100644 +--- a/service/attestation/attestation-types/Cargo.toml ++++ b/service/attestation/attestation-types/Cargo.toml +@@ -5,4 +5,13 @@ edition = "2021" + + [dependencies] + serde = { version = "1.0", features = ["derive"] } +-serde_json = "1.0" +\ No newline at end of file ++serde_json = "1.0" ++regorus = "0.2.8" ++base64 = "0.22.1" ++tokio = { version = "1.43.0", features = ["full"] } ++futures = "0.3.31" ++async-trait = "0.1.85" ++async-recursion = "1.1.1" ++anyhow = "1.0.95" ++thiserror = "2.0.10" ++log = "0.4.22" +diff --git a/service/attestation/attestation-types/src/lib.rs b/service/attestation/attestation-types/src/lib.rs +index 67dcf9f..82c124c 100644 +--- a/service/attestation/attestation-types/src/lib.rs ++++ b/service/attestation/attestation-types/src/lib.rs +@@ -9,7 +9,11 @@ + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ +-use serde::{Serialize, Deserialize}; ++ ++pub mod resource; ++pub mod service; ++ ++use serde::{Deserialize, Serialize}; + use serde_json::Value; + + pub const SESSION_TIMEOUT_MIN: i64 = 1; +@@ -25,10 +29,10 @@ pub struct VirtccaEvidence { + pub enum TeeType { + Itrustee = 1, + Virtcca, ++ Rustcca, + Invalid, + } + +- + #[derive(Debug, Serialize, Deserialize)] + pub struct Evidence { + pub tee: TeeType, +@@ -51,4 +55,4 @@ pub struct Claims { + pub evaluation_reports: EvlResult, + pub tee: String, + pub tcb_status: Value, +-} +\ No newline at end of file ++} +diff --git a/service/attestation/attestation-types/src/resource/admin/mod.rs b/service/attestation/attestation-types/src/resource/admin/mod.rs +new file mode 100644 +index 0000000..6f74a57 +--- /dev/null ++++ b/service/attestation/attestation-types/src/resource/admin/mod.rs +@@ -0,0 +1,59 @@ ++/* ++ * 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 simple; ++ ++use crate::resource::{error::Result, policy::PolicyLocation, Resource, ResourceLocation}; ++use async_trait::async_trait; ++ ++#[async_trait] ++pub trait ResourceAdminInterface: ResourcePolicyAdminInterface + Send + Sync { ++ /// Get resource from the storage ++ async fn get_resource(&self, location: ResourceLocation) -> Result; ++ /// Traverse and get resource list in particular vendor. ++ async fn list_resource(&self, vendor: &str) -> Result>; ++ /// Add new resource. If the resource already exists, error will be thrown. ++ async fn add_resource( ++ &self, ++ _location: ResourceLocation, ++ _content: String, ++ _policy: Vec, ++ ) -> Result<()>; ++ /// Modify the content of specific resource. ++ async fn modify_resource(&self, _location: ResourceLocation, _content: String) -> Result<()>; ++ /// Delete resource. ++ async fn del_resource(&self, _location: ResourceLocation) -> Result<()>; ++ /// Bind policy with resource. ++ async fn bind_policy(&self, _location: ResourceLocation, _policy: Vec) -> Result<()>; ++ /// Unbind policy with resource. ++ async fn unbind_policy(&self, _location: ResourceLocation, _policy: Vec) -> Result<()>; ++ /// Evaluate resource according the claims. ++ async fn evaluate_resource(&self, _location: ResourceLocation, _claim: &str) -> Result; ++} ++ ++#[async_trait] ++pub trait ResourcePolicyAdminInterface: Send + Sync { ++ /// Create a policy file and write the content inside the file. If it already exists, override it. ++ async fn add_policy(&self, _policy: PolicyLocation, _content: &str) -> Result<()>; ++ /// Read the policy content from the file. ++ async fn get_policy(&self, _policy: PolicyLocation) -> Result; ++ /// Delete the policy file. ++ async fn delete_policy(&self, _policy: PolicyLocation) -> Result<()>; ++ /// Get all existing policy files. ++ async fn get_all_policies(&self) -> Result>; ++ /// Get all policy files of a vendor. ++ async fn get_all_policies_in_vendor(&self, _vendor: &str) -> Result>; ++ /// Clear all policy files. ++ async fn clear_all_policies(&self) -> Result<()>; ++ /// Clear all policy files in vendor. ++ async fn clear_all_policies_in_vendor(&self, _vendor: &str) -> Result<()>; ++} +diff --git a/service/attestation/attestation-types/src/resource/admin/simple.rs b/service/attestation/attestation-types/src/resource/admin/simple.rs +new file mode 100644 +index 0000000..5967be9 +--- /dev/null ++++ b/service/attestation/attestation-types/src/resource/admin/simple.rs +@@ -0,0 +1,256 @@ ++/* ++ * 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::resource::admin::ResourceAdminInterface; ++use crate::resource::error::{ResourceError, Result}; ++use crate::resource::policy::opa::OpenPolicyAgent; ++use crate::resource::policy::{PolicyEngine, PolicyLocation}; ++use crate::resource::storage::simple::SimpleStorage; ++use crate::resource::storage::StorageEngine; ++use crate::resource::ResourceLocation; ++use anyhow::Context; ++use async_trait::async_trait; ++use std::path::PathBuf; ++use std::sync::Arc; ++use tokio::sync::Mutex; ++ ++use super::{Resource, ResourcePolicyAdminInterface}; ++ ++pub struct SimpleResourceAdmin { ++ storage_engine: Arc>, ++ policy_engine: Arc>, ++} ++ ++impl SimpleResourceAdmin { ++ pub fn new(storage_base: PathBuf, policy_base: PathBuf) -> Self { ++ SimpleResourceAdmin { ++ storage_engine: Arc::new(Mutex::new(SimpleStorage::new(storage_base))), ++ policy_engine: Arc::new(Mutex::new(OpenPolicyAgent::new(policy_base))), ++ } ++ } ++ ++ pub fn default() -> Self { ++ SimpleResourceAdmin { ++ storage_engine: Arc::new(Mutex::new(SimpleStorage::default())), ++ policy_engine: Arc::new(Mutex::new(OpenPolicyAgent::default())), ++ } ++ } ++} ++ ++#[async_trait] ++impl ResourceAdminInterface for SimpleResourceAdmin { ++ async fn get_resource(&self, location: ResourceLocation) -> Result { ++ self.storage_engine.lock().await.get(location).await ++ } ++ ++ async fn list_resource(&self, vendor: &str) -> Result> { ++ self.storage_engine.lock().await.list(vendor).await ++ } ++ ++ async fn evaluate_resource(&self, location: ResourceLocation, claims: &str) -> Result { ++ let resource = self ++ .get_resource(location.clone()) ++ .await ++ .context("get resource failed") ++ .map_err(|e| { ++ log::debug!("{}", e); ++ e ++ })?; ++ Ok(self ++ .policy_engine ++ .lock() ++ .await ++ .evaluate(location, claims, resource.get_policy()) ++ .await ++ .context("evaluate failed") ++ .map_err(|e| { ++ log::debug!("{}", e); ++ e ++ })?) ++ } ++ ++ // If unmatched policy is found, aborting the adding procedure. ++ async fn add_resource( ++ &self, ++ location: ResourceLocation, ++ content: String, ++ policy: Vec, ++ ) -> Result<()> { ++ let mut legal_policy: Vec = vec![]; ++ for p in policy { ++ let p = match PolicyLocation::try_from(p.clone()) { ++ Ok(p) => p, ++ Err(e) => { ++ log::warn!("Failed to parse policy '{}': {}", p, e); ++ continue; ++ } ++ }; ++ if !location.check_policy_legal(&p) { ++ return Err(ResourceError::UnmatchedPolicyResource( ++ location.to_string(), ++ p.to_string(), ++ )); ++ } ++ legal_policy.push(p.clone()); ++ } ++ let resource = Resource::new(content, legal_policy); ++ self.storage_engine ++ .lock() ++ .await ++ .store(location, resource) ++ .await ++ } ++ ++ async fn del_resource(&self, location: ResourceLocation) -> Result<()> { ++ self.storage_engine.lock().await.delete(location).await ++ } ++ ++ // If unmatched policy is found, aborting the binding procedure. ++ async fn bind_policy(&self, location: ResourceLocation, policy: Vec) -> Result<()> { ++ let mut legal_policy: Vec = vec![]; ++ for p in policy.iter() { ++ if let Ok(p) = p.parse::() { ++ if !location.check_policy_legal(&p) { ++ return Err(ResourceError::UnmatchedPolicyResource( ++ location.to_string(), ++ p.to_string(), ++ )); ++ } ++ legal_policy.push(p); ++ } ++ } ++ self.storage_engine ++ .lock() ++ .await ++ .bind_policies(location, legal_policy) ++ .await ++ } ++ ++ // If unmatched policy is found, aborting the unbinding procedure. ++ async fn unbind_policy(&self, location: ResourceLocation, policy: Vec) -> Result<()> { ++ let mut legal_policy: Vec = vec![]; ++ for p in policy.iter() { ++ let p = p.parse::()?; ++ if !location.check_policy_legal(&p) { ++ return Err(ResourceError::UnmatchedPolicyResource( ++ location.to_string(), ++ p.to_string(), ++ )); ++ } ++ legal_policy.push(p); ++ } ++ self.storage_engine ++ .lock() ++ .await ++ .unbind_policies(location, legal_policy) ++ .await ++ } ++ ++ async fn modify_resource(&self, location: ResourceLocation, content: String) -> Result<()> { ++ self.storage_engine ++ .lock() ++ .await ++ .modify(location, content) ++ .await ++ } ++} ++ ++#[async_trait] ++impl ResourcePolicyAdminInterface for SimpleResourceAdmin { ++ /// Create a policy file and write the content inside the file. If it already exists, override it. ++ async fn add_policy(&self, path: PolicyLocation, policy: &str) -> Result<()> { ++ self.policy_engine ++ .lock() ++ .await ++ .add_policy(path, policy) ++ .await ++ } ++ /// Read the policy content from the file. ++ async fn get_policy(&self, path: PolicyLocation) -> Result { ++ self.policy_engine.lock().await.get_policy(path).await ++ } ++ /// Delete the policy file. ++ async fn delete_policy(&self, path: PolicyLocation) -> Result<()> { ++ self.policy_engine.lock().await.delete_policy(path).await ++ } ++ /// Get all existing policy files. ++ async fn get_all_policies(&self) -> Result> { ++ self.policy_engine.lock().await.get_all_policy().await ++ } ++ /// Get all policy files of a vendor. ++ async fn get_all_policies_in_vendor(&self, vendor: &str) -> Result> { ++ self.policy_engine ++ .lock() ++ .await ++ .get_all_policy_in_vendor(vendor) ++ .await ++ } ++ /// Clear all policy files. ++ async fn clear_all_policies(&self) -> Result<()> { ++ self.policy_engine.lock().await.clear_all_policy().await ++ } ++ /// Clear all policy files in vendor. ++ async fn clear_all_policies_in_vendor(&self, vendor: &str) -> Result<()> { ++ self.policy_engine ++ .lock() ++ .await ++ .clear_all_policy_in_vendor(vendor) ++ .await ++ } ++} ++ ++#[cfg(test)] ++mod tests { ++ use crate::resource::{admin::ResourceAdminInterface, ResourceLocation}; ++ use std::env; ++ use tokio::runtime::Runtime; ++ ++ #[test] ++ fn test_admin_unbind_policy() { ++ let cwd = env::current_dir().unwrap(); ++ let storage_base = cwd.join("storage"); ++ let policy_base = cwd.join("policy"); ++ let tmp_vendor = "test_admin_unbind_policy"; ++ let tmp_resource = "test"; ++ let vendor_path = storage_base.join(tmp_vendor); ++ let resource_path = storage_base.join(tmp_vendor).join(tmp_resource); ++ let admin = super::SimpleResourceAdmin::new(storage_base.clone(), policy_base.clone()); ++ std::fs::create_dir_all(&vendor_path).unwrap(); ++ std::fs::File::create(&resource_path).unwrap(); ++ let resource = r#"{ ++ "content": "hello", ++ "policy": ["test_admin_unbind_policy/c.rego", "test_admin_unbind_policy/a.rego", "default/b.rego", "test_admin_unbind_policy/b.rego"] ++}"#; ++ std::fs::write(&resource_path, resource).unwrap(); ++ ++ let location = ++ ResourceLocation::new(Some(tmp_vendor.to_string()), tmp_resource.to_string()); ++ let unbind_policy = vec![ ++ "default/b.rego".to_string(), ++ "test_admin_unbind_policy/b.rego".to_string(), ++ ]; ++ ++ let runtime = Runtime::new().unwrap(); ++ runtime ++ .block_on(admin.unbind_policy(location.clone(), unbind_policy)) ++ .unwrap(); ++ let r = runtime.block_on(admin.get_resource(location)).unwrap(); ++ let content = r.to_string().unwrap(); ++ println!("{}", r.to_string().unwrap()); ++ assert_eq!( ++ content, ++ r#"{"content":"hello","policy":["test_admin_unbind_policy/a.rego","test_admin_unbind_policy/c.rego"]}"# ++ ); ++ ++ std::fs::remove_dir_all(&storage_base).unwrap(); ++ } ++} +diff --git a/service/attestation/attestation-types/src/resource/error.rs b/service/attestation/attestation-types/src/resource/error.rs +new file mode 100644 +index 0000000..296aae8 +--- /dev/null ++++ b/service/attestation/attestation-types/src/resource/error.rs +@@ -0,0 +1,45 @@ ++/* ++ * 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::path::StripPrefixError; ++use thiserror::Error; ++ ++pub type Result = std::result::Result; ++ ++#[derive(Error, Debug)] ++pub enum ResourceError { ++ #[error("Trait is not implemented.")] ++ NotImplemented, ++ #[error("Policy is missing.")] ++ PolicyMissing, ++ #[error("Failed to load policy: {0}")] ++ LoadPolicy(#[from] anyhow::Error), ++ #[error("Resource error: {0}")] ++ ResourceError(#[from] std::io::Error), ++ #[error("Illegal resource path: {0}")] ++ IllegalResource(String), ++ #[error("Invalid resource content: {0}")] ++ ResourceFromUtf8(#[from] std::string::FromUtf8Error), ++ #[error("Serde deserialize failure: {0}")] ++ SerdeError(#[from] serde_json::Error), ++ #[error("Illegal policy location path: {0}")] ++ IllegalPolicyLocation(String), ++ #[error("Unmatched vendor between resource {0} and policy {1}")] ++ UnmatchedPolicyResource(String, String), ++ #[error("Convert error: {0}")] ++ IoError(#[from] core::convert::Infallible), ++ #[error("Strip Prefix fail: {0}")] ++ StripPrefix(#[from] StripPrefixError), ++ #[error("Illegal policy suffix: {0}")] ++ IllegalPolicySuffix(String), ++ #[error("Resource already exist: {0}")] ++ ResourceExist(String), ++} +diff --git a/service/attestation/attestation-types/src/resource/mod.rs b/service/attestation/attestation-types/src/resource/mod.rs +new file mode 100644 +index 0000000..037c086 +--- /dev/null ++++ b/service/attestation/attestation-types/src/resource/mod.rs +@@ -0,0 +1,145 @@ ++/* ++ * 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 admin; ++pub mod error; ++pub mod policy; ++pub mod storage; ++ ++pub(crate) mod utils; ++ ++use crate::resource::error::{ResourceError, Result}; ++use crate::resource::policy::PolicyLocation; ++use anyhow::Context; ++use serde::{Deserialize, Serialize}; ++use std::{fmt::Display, path::PathBuf, str::FromStr}; ++ ++pub(crate) const DEFAULT_VENDOR_BASE: &str = "oeas"; ++ ++/// This struct indicates unique resource location under specific base directory. ++/// Base directory should be maintained by the resource management engine. ++#[derive(Deserialize, Serialize, Debug, Clone)] ++pub struct ResourceLocation { ++ pub vendor: Option, ++ pub path: String, ++} ++ ++impl std::convert::From for String { ++ fn from(value: ResourceLocation) -> Self { ++ format!("{}", value) ++ } ++} ++ ++impl std::convert::TryFrom for PathBuf { ++ type Error = ResourceError; ++ ++ fn try_from(value: ResourceLocation) -> std::result::Result { ++ let path: String = value.into(); ++ Ok(PathBuf::from_str(&path)?) ++ } ++} ++ ++impl Display for ResourceLocation { ++ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ++ write!( ++ f, ++ "{}/{}", ++ self.vendor ++ .clone() ++ .unwrap_or(DEFAULT_VENDOR_BASE.to_string()), ++ self.path, ++ ) ++ } ++} ++ ++impl ResourceLocation { ++ pub fn new(vendor: Option, path: String) -> Self { ++ Self { vendor, path } ++ } ++ ++ /// If the vendor if resource or vendor is None, it means using the 'default' vendor. ++ /// ++ /// If the vendor of policy is 'default', the check always succeed. ++ /// Otherwise the vendor of policy should be the same with resource. ++ /// ++ pub fn check_policy_legal(&self, policy: &PolicyLocation) -> bool { ++ let policy_vendor = if policy.vendor.is_none() { ++ return true; ++ } else { ++ policy.vendor.clone().unwrap() ++ }; ++ ++ if policy_vendor.as_str() == DEFAULT_VENDOR_BASE { ++ return true; ++ } ++ ++ match self.vendor.as_ref() { ++ None => false, ++ Some(v) => v == &policy_vendor, ++ } ++ } ++} ++ ++/// Policy should be expressed like 'vendor/xxx.rego' ++#[derive(Deserialize, Serialize, Debug)] ++pub struct Resource { ++ pub(crate) content: String, ++ pub(crate) policy: Vec, ++} ++ ++impl Resource { ++ pub(crate) fn new(content: String, policy: Vec) -> Self { ++ let mut r = Self { ++ content, ++ policy: vec![], ++ }; ++ r.set_policy(policy); ++ r ++ } ++ ++ pub fn get_content(&self) -> String { ++ self.content.clone() ++ } ++ ++ /// The illegal policy will be ignored and throw warning message. ++ pub fn get_policy(&self) -> Vec { ++ let mut ret: Vec = vec![]; ++ for s in self.policy.iter() { ++ let p = PolicyLocation::try_from(s.clone()); ++ match p { ++ Ok(p) => ret.push(p), ++ Err(_) => { ++ log::warn!("Illegal policy: {}", s); ++ } ++ } ++ } ++ ret ++ } ++ ++ pub fn set_policy(&mut self, policy: Vec) { ++ let policy = policy.iter().map(|p| format!("{}", p)).collect(); ++ self.policy = policy; ++ } ++ ++ pub(crate) async fn read_from_file(path: PathBuf) -> Result { ++ let content = tokio::fs::read(path) ++ .await ++ .context("failed to add resource")?; ++ Ok(serde_json::from_str( ++ &String::from_utf8(content).context("from utf8 error")?, ++ )?) ++ } ++ ++ pub(crate) fn to_string(&self) -> Result { ++ Ok(serde_json::to_string(self)?) ++ } ++} +diff --git a/service/attestation/attestation-types/src/resource/policy/mod.rs b/service/attestation/attestation-types/src/resource/policy/mod.rs +new file mode 100644 +index 0000000..d7ae01d +--- /dev/null ++++ b/service/attestation/attestation-types/src/resource/policy/mod.rs +@@ -0,0 +1,128 @@ ++/* ++ * 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(crate) mod opa; ++ ++use crate::resource::error::ResourceError; ++use crate::resource::error::Result; ++use crate::resource::ResourceLocation; ++use crate::resource::DEFAULT_VENDOR_BASE; ++use async_trait::async_trait; ++use serde::{Deserialize, Serialize}; ++use std::fmt::Display; ++use std::path::PathBuf; ++use std::str::FromStr; ++ ++/// This structure indicates unique policy location under specific base directory. ++/// The base directory should be maintained by the policy management engine. ++/// If vendor is none, it should keep the same with the resource vendor. ++/// ++/// To simplify the expression, the policy location can be expressed like 'vendor/policy.rego'. ++#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] ++pub struct PolicyLocation { ++ pub vendor: Option, ++ pub id: String, ++} ++ ++impl std::convert::From for String { ++ fn from(value: PolicyLocation) -> Self { ++ format!("{}", value) ++ } ++} ++ ++impl std::convert::From<&PolicyLocation> for String { ++ fn from(value: &PolicyLocation) -> Self { ++ format!("{}", value) ++ } ++} ++ ++impl std::convert::TryFrom for PathBuf { ++ type Error = ResourceError; ++ ++ fn try_from(value: PolicyLocation) -> std::result::Result { ++ let path: String = value.into(); ++ Ok(PathBuf::from_str(&path)?) ++ } ++} ++ ++impl std::convert::TryFrom<&PolicyLocation> for PathBuf { ++ type Error = ResourceError; ++ ++ fn try_from(value: &PolicyLocation) -> std::result::Result { ++ Ok(PathBuf::from_str(&format!("{}", value))?) ++ } ++} ++ ++impl Display for PolicyLocation { ++ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { ++ write!( ++ f, ++ "{}/{}", ++ self.vendor ++ .clone() ++ .unwrap_or(DEFAULT_VENDOR_BASE.to_string()), ++ self.id, ++ ) ++ } ++} ++ ++impl std::convert::TryFrom for PolicyLocation { ++ type Error = ResourceError; ++ fn try_from(value: String) -> Result { ++ let parts: Vec<&str> = value.split('/').collect(); ++ if parts.len() != 2 { ++ return Err(ResourceError::IllegalPolicyLocation(value)); ++ } ++ ++ let vendor = match parts[0] { ++ DEFAULT_VENDOR_BASE => None, ++ other => Some(other.to_string()), ++ }; ++ let id = parts[1].to_string(); ++ ++ Ok(PolicyLocation { vendor, id }) ++ } ++} ++ ++impl FromStr for PolicyLocation { ++ type Err = ResourceError; ++ ++ fn from_str(s: &str) -> Result { ++ TryFrom::try_from(s.to_string()) ++ } ++} ++ ++/// Manage the policy files and evaluate the legality of resource ++#[async_trait] ++pub(crate) trait PolicyEngine: Send + Sync { ++ /// Given the resource location and claims, read the resource content from the storage and evaluate the resource according to the claims. ++ async fn evaluate( ++ &self, ++ _resource: ResourceLocation, ++ _claims: &str, ++ _policy: Vec, ++ ) -> Result; ++ /// Create a policy file and write the content inside the file. If it already exists, override it. ++ async fn add_policy(&self, _path: PolicyLocation, _policy: &str) -> Result<()>; ++ /// Read the policy content from the file. ++ async fn get_policy(&self, _path: PolicyLocation) -> Result; ++ /// Delete the policy file. ++ async fn delete_policy(&self, _path: PolicyLocation) -> Result<()>; ++ /// Get all existing policy files. ++ async fn get_all_policy(&self) -> Result>; ++ /// Get all policy files of a vendor. ++ async fn get_all_policy_in_vendor(&self, _vendor: &str) -> Result>; ++ /// Clear all policy files. ++ async fn clear_all_policy(&self) -> Result<()>; ++ /// Clear all policy files in vendor. ++ async fn clear_all_policy_in_vendor(&self, _vendor: &str) -> Result<()>; ++} +diff --git a/service/attestation/attestation-types/src/resource/policy/opa/mod.rs b/service/attestation/attestation-types/src/resource/policy/opa/mod.rs +new file mode 100644 +index 0000000..0ec506a +--- /dev/null ++++ b/service/attestation/attestation-types/src/resource/policy/opa/mod.rs +@@ -0,0 +1,321 @@ ++/* ++ * 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 super::PolicyLocation; ++use crate::resource::{ ++ error::{ResourceError, Result}, ++ policy::PolicyEngine, ++ ResourceLocation, DEFAULT_VENDOR_BASE, ++}; ++use anyhow::{bail, Context}; ++use async_trait::async_trait; ++use std::path::PathBuf; ++ ++pub(crate) const DEFAULT_RESOURCE_POLICY_DIR: &str = ++ "/run/attestation/attestation-service/resource/policy/"; ++pub(crate) const DEFAULT_RESOURCE_VIRTCCA_DEFAULT_POLICY: &str = "virtcca.rego"; ++ ++pub(crate) struct OpenPolicyAgent { ++ base: PathBuf, ++} ++ ++impl OpenPolicyAgent { ++ pub(crate) fn new(base: PathBuf) -> Self { ++ OpenPolicyAgent { base } ++ } ++ ++ pub fn default() -> Self { ++ Self::new(PathBuf::from(DEFAULT_RESOURCE_POLICY_DIR)) ++ } ++} ++ ++#[async_trait] ++impl PolicyEngine for OpenPolicyAgent { ++ async fn evaluate( ++ &self, ++ resource: ResourceLocation, ++ claim: &str, ++ policy: Vec, ++ ) -> Result { ++ let mut engine = regorus::Engine::new(); ++ let mut eval_targets: Vec = vec![]; ++ ++ if policy.is_empty() { ++ /* Apply default policy according to the tee type from the claims. */ ++ let claim_json: serde_json::Value = serde_json::from_str(claim)?; ++ if let Some(tee) = claim_json.get("tee") { ++ if let Some(tee_str) = tee.as_str() { ++ match tee_str { ++ "vcca" => { ++ engine ++ .add_policy_from_file( ++ self.base ++ .join(DEFAULT_VENDOR_BASE) ++ .join(DEFAULT_RESOURCE_VIRTCCA_DEFAULT_POLICY), ++ ) ++ .context("failed to add policy from file")?; ++ let vendor = DEFAULT_VENDOR_BASE; ++ let id = match DEFAULT_RESOURCE_VIRTCCA_DEFAULT_POLICY ++ .strip_suffix(".rego") ++ { ++ Some(v) => v, ++ None => { ++ log::debug!( ++ "Invalid default policy id '{}'", ++ DEFAULT_RESOURCE_VIRTCCA_DEFAULT_POLICY ++ ); ++ return Err(ResourceError::IllegalPolicySuffix( ++ DEFAULT_RESOURCE_VIRTCCA_DEFAULT_POLICY.to_string(), ++ )); ++ } ++ }; ++ eval_targets.push(format!("data.{}.{}.allow", vendor, id)) ++ } ++ _ => {} ++ } ++ } ++ } ++ } ++ ++ for file in policy.iter() { ++ let sub_id = match file.id.strip_suffix(".rego") { ++ Some(v) => v, ++ None => { ++ log::debug!("Invalid policy id '{}'", file); ++ return Err(ResourceError::IllegalPolicySuffix(file.to_string())); ++ } ++ }; ++ let p: PathBuf = file.try_into()?; ++ if let Err(e) = engine.add_policy_from_file(self.base.join(p)) { ++ log::debug!("Failed to add policy: {}", e); ++ return Err(e.into()); ++ } ++ // .context("failed to add policy from file")?; ++ eval_targets.push(format!( ++ "data.{}.{}.allow", ++ file.vendor ++ .clone() ++ .unwrap_or(DEFAULT_VENDOR_BASE.to_string()), ++ sub_id ++ )) ++ } ++ log::debug!("Evaluate query targest: {:?}", eval_targets); ++ if let Err(e) = engine.add_data_json(&format!("{{\"resource\":\"{}\"}}", resource)) { ++ log::debug!("Failed to add resource data: {}", e); ++ return Err(e.into()); ++ } ++ if let Err(e) = engine.set_input_json(claim) { ++ log::debug!("Failed to set input claim: {}", e); ++ return Err(e.into()); ++ } ++ ++ let mut ret = true; ++ ++ for eval in eval_targets { ++ let v = match engine.eval_bool_query(eval.clone(), false) { ++ Ok(v) => v, ++ Err(e) => { ++ log::debug!("Failed to evaluate {}: {}", eval, e); ++ return Err(e.into()); ++ } ++ }; ++ log::debug!("Evaluate {} = {}", eval, v); ++ ret = ret && v; ++ } ++ ++ Ok(ret) ++ } ++ ++ async fn get_policy(&self, path: PolicyLocation) -> Result { ++ let p = self.base.join(format!("{}", path)); ++ let raw = tokio::fs::read(p).await?; ++ Ok(String::from_utf8(raw)?) ++ } ++ ++ async fn add_policy(&self, path: PolicyLocation, policy: &str) -> Result<()> { ++ let p = self.base.join(format!("{}", path)); ++ if let Some(parent) = p.parent() { ++ if let Err(e) = tokio::fs::create_dir_all(parent).await { ++ log::warn!( ++ "Failed to create vendor directory for policy '{}': {}", ++ path, ++ e ++ ); ++ } ++ } ++ tokio::fs::write(p, policy.as_bytes()).await?; ++ Ok(()) ++ } ++ ++ async fn delete_policy(&self, path: PolicyLocation) -> Result<()> { ++ let p = self.base.join(format!("{}", path)); ++ tokio::fs::remove_file(p).await?; ++ Ok(()) ++ } ++ ++ async fn get_all_policy(&self) -> Result> { ++ let mut ret: Vec = vec![]; ++ let mut dir = tokio::fs::read_dir(&self.base).await?; ++ while let Some(d) = dir.next_entry().await? { ++ match d.file_type().await { ++ Ok(t) => { ++ if !t.is_dir() { ++ continue; ++ } ++ } ++ Err(_) => { ++ continue; ++ } ++ } ++ ++ let vendor = match d.file_name().into_string() { ++ Ok(s) => s, ++ Err(s) => { ++ log::warn!("Illegal policy vendor directory '{:?}'", s); ++ continue; ++ } ++ }; ++ ++ let mut several = match self.get_all_policy_in_vendor(&vendor).await { ++ Ok(v) => v, ++ Err(e) => { ++ log::warn!("Failed to get policy from vendor '{}': {}", vendor, e); ++ continue; ++ } ++ }; ++ ++ ret.append(&mut several); ++ } ++ ++ Ok(ret) ++ } ++ ++ async fn get_all_policy_in_vendor(&self, vendor: &str) -> Result> { ++ let vendor_dir = self.base.join(&vendor); ++ let mut dir = tokio::fs::read_dir(vendor_dir).await?; ++ let mut ret: Vec = vec![]; ++ while let Some(d) = dir.next_entry().await? { ++ if let Ok(t) = d.file_type().await { ++ if !t.is_file() { ++ continue; ++ } ++ } ++ ++ let rego = match d.file_name().into_string() { ++ Ok(s) => s, ++ Err(s) => { ++ log::warn!("Illegal policy file name '{:?}'", s); ++ continue; ++ } ++ }; ++ if !rego.ends_with("rego") { ++ continue; ++ } ++ ++ ret.push(PolicyLocation { ++ vendor: if vendor == DEFAULT_VENDOR_BASE { ++ None ++ } else { ++ Some(vendor.to_string()) ++ }, ++ id: rego, ++ }); ++ } ++ ++ Ok(ret) ++ } ++ ++ async fn clear_all_policy(&self) -> Result<()> { ++ let mut dir = tokio::fs::read_dir(&self.base).await?; ++ while let Some(d) = dir.next_entry().await? { ++ match d.file_type().await { ++ Ok(t) => { ++ if !t.is_dir() { ++ continue; ++ } ++ } ++ Err(_) => { ++ continue; ++ } ++ } ++ ++ match d.file_name().into_string() { ++ Ok(s) => { ++ if let Err(e) = self.clear_all_policy_in_vendor(&s).await { ++ log::warn!("Failed to clear vendor '{}': {}", s, e); ++ } ++ } ++ Err(e) => { ++ log::warn!("Illegal vendor directory name '{:?}'", e); ++ continue; ++ } ++ } ++ } ++ Ok(()) ++ } ++ ++ async fn clear_all_policy_in_vendor(&self, vendor: &str) -> Result<()> { ++ let vendor_dir = self.base.join(&vendor); ++ let md = tokio::fs::metadata(&vendor_dir) ++ .await ++ .context("invalid vendor")?; ++ if md.is_dir() { ++ tokio::fs::remove_dir_all(vendor_dir).await?; ++ } ++ Ok(()) ++ } ++} ++ ++#[cfg(test)] ++mod tests { ++ use tokio::runtime; ++ ++ use super::ResourceLocation; ++ use super::{OpenPolicyAgent, PolicyEngine}; ++ ++ #[test] ++ fn test_evaluate() { ++ let pwd = std::env::current_dir().expect("failed to get pwd"); ++ let opa = OpenPolicyAgent::new(pwd.join("src/policy/opa")); ++ let resource = ResourceLocation::new(None, "b/p/f".to_string()); ++ let claims = r#" ++{ ++ "iss": "oeas", ++ "iat": 1735635443, ++ "nbf": 1735635443, ++ "exp": 1735635743, ++ "evaluation_reports": { ++ "eval_result": true, ++ "policy": [], ++ "report": { ++ "default_vcca.rego": "{\"vcca.cvm.rim\":\"1ee366339c8245a34a8ad9d27a0b912a588af7da8aef514ae8dec22746956dd1\"}", ++ "ima": {} ++ } ++ }, ++ "tee": "vcca", ++ "tcb_status": { ++ "vcca.cvm.challenge": "586667776b4972524b58684550524f384771654c7244695356485134715f372d4e36375064587a50457763000000000000000000000000000000000000000000", ++ "vcca.cvm.rem.0": "927b62bc7f4d9fd03afd0b9b2fe8832004b570b4c4bffc2949c4e461b0a0ff63", ++ "vcca.cvm.rem.1": "0000000000000000000000000000000000000000000000000000000000000000", ++ "vcca.cvm.rem.2": "0000000000000000000000000000000000000000000000000000000000000000", ++ "vcca.cvm.rem.3": "0000000000000000000000000000000000000000000000000000000000000000", ++ "vcca.cvm.rim": "1ee366339c8245a34a8ad9d27a0b912a588af7da8aef514ae8dec22746956dd1", ++ "vcca.cvm.rpv": "01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", ++ "vcca.platform": "" ++ } ++}"#; ++ let policy = vec![]; ++ let rt = runtime::Runtime::new().unwrap(); ++ let r = rt.block_on(opa.evaluate(resource, claims, policy)); ++ assert_eq!(r.unwrap(), true); ++ } ++} +diff --git a/service/attestation/attestation-types/src/resource/policy/opa/virtcca.rego b/service/attestation/attestation-types/src/resource/policy/opa/virtcca.rego +new file mode 100644 +index 0000000..fa32e38 +--- /dev/null ++++ b/service/attestation/attestation-types/src/resource/policy/opa/virtcca.rego +@@ -0,0 +1,12 @@ ++# The naming scheme of package is ".". ++# ++# The policy location of the corresponding policy file should be "/.rego". ++# ++ ++package oeas.virtcca ++ ++default allow = false ++ ++allow { ++ input["tee"] == "vcca" ++} +\ No newline at end of file +diff --git a/service/attestation/attestation-types/src/resource/storage/mod.rs b/service/attestation/attestation-types/src/resource/storage/mod.rs +new file mode 100644 +index 0000000..fd7b0c7 +--- /dev/null ++++ b/service/attestation/attestation-types/src/resource/storage/mod.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. ++ */ ++ ++pub(crate) mod simple; ++ ++use crate::resource::error::ResourceError; ++use crate::resource::error::Result; ++use crate::resource::policy::PolicyLocation; ++use crate::resource::Resource; ++use crate::resource::ResourceLocation; ++use async_trait::async_trait; ++ ++#[async_trait] ++pub(crate) trait StorageEngine: StorageOp + PolicyOp {} ++ ++#[async_trait] ++pub(crate) trait StorageOp: Send + Sync { ++ /// Get the resource inside the storage and return a structure instance. ++ async fn get(&self, location: ResourceLocation) -> Result; ++ /// Traverse and collect resource list in particular vendor. ++ async fn list(&self, vendor: &str) -> Result>; ++ /// Create a new resource if it does not exist. If the resource already exists, error will be thrown. ++ async fn store(&self, location: ResourceLocation, resource: Resource) -> Result<()>; ++ /// Override the content field in the resource, while keep other fields the same. ++ async fn modify(&self, location: ResourceLocation, content: String) -> Result<()>; ++ /// Delete the resource inside the storage. ++ async fn delete(&self, location: ResourceLocation) -> Result<()>; ++ /// Flush the buffer into the storage ++ async fn flush(&self) -> Result<()> { ++ Err(ResourceError::NotImplemented) ++ } ++} ++ ++#[async_trait] ++pub(crate) trait PolicyOp: StorageOp + Send + Sync { ++ /// Clear the original policy and set the new ones. ++ async fn set_policies( ++ &self, ++ location: ResourceLocation, ++ policy: Vec, ++ ) -> Result<()>; ++ /// Get all policy from the resource. ++ async fn get_all_policies(&self, location: ResourceLocation) -> Result>; ++ /// Clear the original policy inside the resource. ++ async fn clea_policies(&self, location: ResourceLocation) -> Result<()>; ++ /// Delete the specific policy from the resource. ++ async fn unbind_policies( ++ &self, ++ location: ResourceLocation, ++ policy: Vec, ++ ) -> Result<()>; ++ /// Append new policy inside the resource. ++ async fn bind_policies( ++ &self, ++ location: ResourceLocation, ++ policies: Vec, ++ ) -> Result<()>; ++} +diff --git a/service/attestation/attestation-types/src/resource/storage/simple.rs b/service/attestation/attestation-types/src/resource/storage/simple.rs +new file mode 100644 +index 0000000..b8fd536 +--- /dev/null ++++ b/service/attestation/attestation-types/src/resource/storage/simple.rs +@@ -0,0 +1,220 @@ ++/* ++ * 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::resource::error::ResourceError; ++use crate::resource::error::Result; ++use crate::resource::policy::PolicyLocation; ++use crate::resource::storage::StorageOp; ++use crate::resource::utils::traverse_regular_file; ++use crate::resource::ResourceLocation; ++use anyhow::Context; ++use async_trait::async_trait; ++use std::path::PathBuf; ++ ++use super::PolicyOp; ++use super::Resource; ++use super::StorageEngine; ++ ++pub(crate) const STORAGE_BASE: &str = "/run/attestation/attestation-service/resource/storage/"; ++ ++pub(crate) struct SimpleStorage { ++ base: PathBuf, ++} ++ ++impl SimpleStorage { ++ pub(crate) fn new(base: PathBuf) -> Self { ++ Self { base } ++ } ++ ++ pub(crate) fn default() -> Self { ++ Self::new(PathBuf::from(STORAGE_BASE)) ++ } ++ ++ /// Resource location can not contain dot characters to avoid visiting parent directory. All the resource is stored under the base directory. ++ fn regular(&self, location: &str) -> Result { ++ /* abandon passing relative path */ ++ if !self.check_legal(location) { ++ return Err(ResourceError::IllegalResource(location.to_string())); ++ } ++ let base = PathBuf::from(&self.base); ++ let path = base.join(location); ++ Ok(path) ++ } ++ ++ fn check_legal(&self, location: &str) -> bool { ++ !location.contains(|c| ['.'].contains(&c)) ++ } ++} ++ ++#[async_trait] ++impl StorageEngine for SimpleStorage {} ++ ++#[async_trait] ++impl StorageOp for SimpleStorage { ++ async fn get(&self, location: ResourceLocation) -> Result { ++ let regularized = self.regular(&format!("{}", location))?; ++ Resource::read_from_file(regularized).await ++ } ++ ++ async fn list(&self, vendor: &str) -> Result> { ++ let vendor_base = self.regular(vendor)?; ++ let resource_list = traverse_regular_file(&vendor_base).await?; ++ let mut ret: Vec = vec![]; ++ for p in resource_list.iter() { ++ let path = p.strip_prefix(&vendor_base)?; ++ let resource = ResourceLocation::new( ++ Some(vendor.to_string()), ++ path.to_str() ++ .ok_or(ResourceError::IllegalResource(format!("{:?}", path)))? ++ .to_string(), ++ ); ++ ret.push(resource); ++ } ++ Ok(ret) ++ } ++ ++ async fn store(&self, location: ResourceLocation, resource: Resource) -> Result<()> { ++ let regularized = self.regular(&format!("{}", location))?; ++ ++ if regularized.exists() { ++ return Err(ResourceError::ResourceExist(location.to_string())); ++ } ++ ++ if let Some(parent) = regularized.parent() { ++ if let Err(e) = tokio::fs::create_dir_all(parent).await { ++ log::warn!( ++ "Failed to create vendor directory for resource '{}': {}", ++ location, ++ e ++ ); ++ } ++ } ++ tokio::fs::write(regularized, serde_json::to_string(&resource)?) ++ .await ++ .context("failed to add resource")?; ++ Ok(()) ++ } ++ ++ async fn modify(&self, location: ResourceLocation, content: String) -> Result<()> { ++ let regularized = self.regular(&format!("{}", location))?; ++ let mut resource = Resource::read_from_file(regularized.clone()).await?; ++ resource.content = content; ++ tokio::fs::write(regularized, resource.to_string()?) ++ .await ++ .context("failed to modify resource")?; ++ Ok(()) ++ } ++ ++ async fn delete(&self, location: ResourceLocation) -> Result<()> { ++ let regularized = self.regular(&format!("{}", location))?; ++ tokio::fs::remove_file(regularized) ++ .await ++ .context("failed to delete resource")?; ++ Ok(()) ++ } ++} ++ ++#[async_trait] ++impl PolicyOp for SimpleStorage { ++ async fn set_policies( ++ &self, ++ location: ResourceLocation, ++ policy: Vec, ++ ) -> Result<()> { ++ let mut resource = self.get(location.clone()).await?; ++ resource.set_policy(policy); ++ self.store(location, resource).await ++ } ++ async fn get_all_policies(&self, location: ResourceLocation) -> Result> { ++ let resource = self.get(location).await?; ++ Ok(resource.get_policy()) ++ } ++ async fn clea_policies(&self, location: ResourceLocation) -> Result<()> { ++ let mut resource = self.get(location.clone()).await?; ++ resource.policy = vec![]; ++ self.store(location, resource).await ++ } ++ async fn unbind_policies( ++ &self, ++ location: ResourceLocation, ++ policy: Vec, ++ ) -> Result<()> { ++ let mut resource = self.get(location.clone()).await?; ++ resource.policy.sort(); ++ for p in policy.iter() { ++ if let Ok(idx) = resource.policy.binary_search(&format!("{}", p)) { ++ resource.policy.remove(idx); ++ } ++ } ++ self.store(location, resource).await ++ } ++ async fn bind_policies( ++ &self, ++ location: ResourceLocation, ++ policy: Vec, ++ ) -> Result<()> { ++ let mut resource = self.get(location.clone()).await?; ++ for p in policy.iter() { ++ resource.policy.push(format!("{}", p)); ++ } ++ self.store(location.clone(), resource).await ++ } ++} ++ ++#[cfg(test)] ++mod tests { ++ use crate::resource::{policy::PolicyLocation, ResourceLocation}; ++ use std::env; ++ use tokio::runtime::Runtime; ++ ++ use super::{PolicyOp, StorageOp}; ++ ++ #[test] ++ fn test_unbind_policies() { ++ let cwd = env::current_dir().unwrap(); ++ let tmp_vendor = "test_unbind_policies"; ++ let tmp_resource = "test"; ++ let vendor_path = cwd.join(tmp_vendor); ++ let resource_path = cwd.join(tmp_vendor).join(tmp_resource); ++ let storage = super::SimpleStorage::new(cwd); ++ std::fs::create_dir_all(&vendor_path).unwrap(); ++ std::fs::File::create(&resource_path).unwrap(); ++ let resource = r#"{ ++ "content": "hello", ++ "policy": ["test_unbind_policies/c.rego", "test_unbind_policies/a.rego", "default/b.rego", "test_unbind_policies/b.rego"] ++}"#; ++ std::fs::write(&resource_path, resource).unwrap(); ++ ++ let location = ++ ResourceLocation::new(Some(tmp_vendor.to_string()), tmp_resource.to_string()); ++ let unbind_policy = vec![ ++ "default/b.rego".parse::().unwrap(), ++ "test_unbind_policies/b.rego" ++ .parse::() ++ .unwrap(), ++ ]; ++ ++ let runtime = Runtime::new().unwrap(); ++ runtime ++ .block_on(storage.unbind_policies(location.clone(), unbind_policy)) ++ .unwrap(); ++ let r = runtime.block_on(storage.get(location)).unwrap(); ++ let content = r.to_string().unwrap(); ++ println!("{}", r.to_string().unwrap()); ++ assert_eq!( ++ content, ++ r#"{"content":"hello","policy":["test_unbind_policies/a.rego","test_unbind_policies/c.rego"]}"# ++ ); ++ ++ std::fs::remove_dir_all(&vendor_path).unwrap(); ++ } ++} +diff --git a/service/attestation/attestation-types/src/resource/utils.rs b/service/attestation/attestation-types/src/resource/utils.rs +new file mode 100644 +index 0000000..ba87c9c +--- /dev/null ++++ b/service/attestation/attestation-types/src/resource/utils.rs +@@ -0,0 +1,32 @@ ++/* ++ * 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::resource::error::Result; ++use async_recursion::async_recursion; ++use std::path::PathBuf; ++ ++#[async_recursion(Sync)] ++pub(crate) async fn traverse_regular_file(base: &PathBuf) -> Result> { ++ let mut entries = tokio::fs::read_dir(base).await?; ++ let mut ret: Vec = vec![]; ++ while let Some(entry) = entries.next_entry().await? { ++ let path = entry.path(); ++ if path.is_dir() { ++ let mut parts = traverse_regular_file(&path).await?; ++ ret.append(&mut parts); ++ } else if path.is_file() { ++ ret.push(path); ++ } ++ } ++ ++ Ok(ret) ++} +diff --git a/service/attestation/attestation-types/src/service.rs b/service/attestation/attestation-types/src/service.rs +new file mode 100644 +index 0000000..a7047e1 +--- /dev/null ++++ b/service/attestation/attestation-types/src/service.rs +@@ -0,0 +1,83 @@ ++/* ++ * 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::resource::{policy::PolicyLocation, ResourceLocation}; ++use serde::{Deserialize, Serialize}; ++ ++#[derive(Debug, Serialize, Deserialize)] ++pub enum GetResourceOp { ++ /// User in TEE environment can get resource content. ++ TeeGet { resource: ResourceLocation }, ++ /// Vendor can only get the list of resource files that are already published in AS. ++ VendorGet { vendor: String }, ++} ++ ++#[derive(Debug, Serialize, Deserialize, Clone)] ++pub enum SetResourceOp { ++ /// Add new resource. ++ /// The vendor of each policy should be 'default' or the same with the resource. ++ /// Otherwise error will be raised. ++ /// ++ /// If the resource already exists, the content will be overrided. ++ Add { ++ content: String, ++ policy: Vec, ++ }, ++ /// Delete specific resource. ++ Delete, ++ /// Modify the content of specific resource. Other fields of the resource will be kept. ++ Modify { content: String }, ++ /// Bind policy to specific resource. ++ /// The vendor of any policy should be 'default' or the same with the resource. ++ /// Otherwise error will be raised. ++ Bind { policy: Vec }, ++ /// Unbind policy of specific resource. ++ /// The vendor of any policy should be 'default' or the same with the resource. ++ /// Otherwise error will be raised. ++ Unbind { policy: Vec }, ++} ++ ++#[derive(Debug, Serialize, Deserialize, Clone)] ++pub struct SetResourceRequest { ++ pub op: SetResourceOp, ++ /// The vendor of the resource should be the same with that granted in the token. ++ pub resource: ResourceLocation, ++} ++ ++#[derive(Debug, Serialize, Deserialize)] ++pub enum GetResourcePolicyOp { ++ /// Get specific policy under a vendor. ++ GetOne { policy: PolicyLocation }, ++ /// Get all policy under different vendors. ++ /// The returned value is a vector of policy identifer, such as '["vendor_A/example.rego", "vendor_B/example.rego"]'. ++ GetAll, ++ /// Get all policy under particular vendor. ++ /// The returned value is a vector of policy identifer, such as '["vendor_A/example_1.rego", "vendor_A/example_2.rego"]'. ++ GetAllInVendor { vendor: String }, ++} ++ ++#[derive(Debug, Serialize, Deserialize)] ++pub enum SetResourcePolicyOp { ++ /// Add new policy file, if it already exists, override its content. ++ /// ++ /// The vendor of policy should be the same with that in the token granted to the user. ++ Add { ++ policy: PolicyLocation, ++ content: String, ++ }, ++ /// Delete particular policy file. ++ /// ++ /// The vendor of policy should be the same with that in the token granted to the user. ++ Delete { policy: PolicyLocation }, ++ /// Clear all policy files of particular vendor. ++ ClearAll { vendor: String }, ++} +-- +2.46.0 + diff --git a/secGear.spec b/secGear.spec index 5c41f32ca92ae3c5daf59805403f617f7793a021..53871b1e425c6a17161660b7b53a4da23353d2dd 100644 --- a/secGear.spec +++ b/secGear.spec @@ -1,6 +1,6 @@ Name: secGear Version: 0.1.0 -Release: 52 +Release: 53 Summary: secGear is an SDK to develop confidential computing apps based on hardware enclave features @@ -98,6 +98,7 @@ Patch84: 0085-fix-multi-thread-request-as-generate-challenge-and-v.patch Patch85: 0086-add-error-type-for-api.patch Patch86: 0087-use-id-when-get-policy.patch Patch87: 0088-fix-evidence-decode-typos.patch +Patch88: 0089-features-support-resource-maitainance.patch BuildRequires: gcc python automake autoconf libtool @@ -150,6 +151,11 @@ Summary: Attestation Service for %{name} Requires: kunpengsecl-attester %description as The %{name}-as is package contains attestation service + +%package ac +Summary: Attestation Client for %{name} +%description ac +The %{name}-ac provides command line tool for attestation service. %endif %prep @@ -159,6 +165,10 @@ cd %{_builddir}/%{name}/service/attestation/attestation-agent/ tar xf %{SOURCE1} cd %{_builddir}/%{name}/service/attestation/attestation-service/ tar xf %{SOURCE1} +cd %{_builddir}/%{name}/service/attestation/attestation-client/ +tar xf %{SOURCE1} +cd %{_builddir}/%{name}/service/attestation/attestation-types/ +tar xf %{SOURCE1} %endif %build @@ -187,6 +197,12 @@ mkdir -p %{_builddir}/%{name}/service/attestation/attestation-service/.cargo/ cp %{_builddir}/%{name}/service/attestation/attestation-agent/.cargo/config.toml %{_builddir}/%{name}/service/attestation/attestation-service/.cargo/ cd %{_builddir}/%{name}/service/attestation/attestation-service/ %{_cargo} build --bins --release +mkdir -p %{_builddir}/%{name}/service/attestation/attestation-client/.cargo/ +cp %{_builddir}/%{name}/service/attestation/attestation-agent/.cargo/config.toml %{_builddir}/%{name}/service/attestation/attestation-client/.cargo/ +cd %{_builddir}/%{name}/service/attestation/attestation-client/ +%{_cargo} build --bins --release +mkdir -p %{_builddir}/%{name}/service/attestation/attestation-types/.cargo/ +cp %{_builddir}/%{name}/service/attestation/attestation-agent/.cargo/config.toml %{_builddir}/%{name}/service/attestation/attestation-types/.cargo/ %endif @@ -228,12 +244,14 @@ install -d %{buildroot}%{_sysconfdir}/attestation/attestation-service/verifier/v install -pm 644 service/attestation/attestation-service/service/attestation-service.conf %{buildroot}%{_sysconfdir}/attestation/attestation-service/ install -pm 644 service/attestation/attestation-service/policy/src/opa/*.rego %{buildroot}%{_sysconfdir}/attestation/attestation-service/policy/ install -pm 751 service/attestation/attestation-service/target/release/attestation-service %{buildroot}/%{_bindir} +install -pm 751 service/attestation/attestation-client/target/release/attestation-client %{buildroot}/%{_bindir} %endif install -pm 644 component/remote_attest/ra_report/sg_ra_report.h %{buildroot}/%{_includedir}/secGear install -pm 644 component/remote_attest/ra_verify/sg_ra_report_verify.h %{buildroot}/%{_includedir}/secGear install -pm 644 component/remote_attest/sg_report_st.h %{buildroot}/%{_includedir}/secGear install -pm 644 component/local_attest/sg_local_attest.h %{buildroot}/%{_includedir}/secGear + pushd %{buildroot} rm `find . -name secgear_helloworld` -rf rm `find . -name secgear_seal_data` -rf @@ -267,6 +285,7 @@ popd %{_bindir}/* %exclude %{_bindir}/attestation-agent %exclude %{_bindir}/attestation-service +%exclude %{_bindir}/attestation-client %{_includedir}/secGear/* %ifarch x86_64 @@ -289,12 +308,18 @@ popd %{_sysconfdir}/attestation/attestation-service/verifier/itrustee %{_sysconfdir}/attestation/attestation-service/verifier/virtcca +%files ac +%{_bindir}/attestation-client + %endif %post systemctl restart rsyslog %changelog +* Sat Feb 15 chenjiayi - 0.1.0-53 +- support resource maintainance + * Tue Nov 26 2024 houmingyong - 0.1.0-52 - fix evidence decode typos diff --git a/vendor.tar.gz b/vendor.tar.gz index d05e360627a57fd97ed156126998414d84267700..2af6924a64ea50fd8f9624d816255864a166981f 100644 Binary files a/vendor.tar.gz and b/vendor.tar.gz differ