diff --git a/0089-add-parse-report-c-interface.patch b/0089-add-parse-report-c-interface.patch deleted file mode 100644 index 805f2033e2bb9a106c4cfa0ce5414f7256616940..0000000000000000000000000000000000000000 --- a/0089-add-parse-report-c-interface.patch +++ /dev/null @@ -1,94 +0,0 @@ -From e835af7ff3667005be6893dedcb46a18452450d2 Mon Sep 17 00:00:00 2001 -From: houmingyong -Date: Mon, 14 Oct 2024 11:35:12 +0800 -Subject: [PATCH] add parse report c interface - -Conflict: remove /attestation-agent/c_header/example.c,c_header/rust_attestation_agent.h - ---- - .../attestation-agent/agent/src/lib.rs | 31 +++++++++++++++++++ - .../attestation-service/verifier/src/lib.rs | 7 +++++ - .../verifier/src/virtcca/mod.rs | 10 ++++++ - 3 files changed, 48 insertions(+) - -diff --git a/service/attestation/attestation-agent/agent/src/lib.rs b/service/attestation/attestation-agent/agent/src/lib.rs -index f1c4510..1164e2a 100644 ---- a/service/attestation/attestation-agent/agent/src/lib.rs -+++ b/service/attestation/attestation-agent/agent/src/lib.rs -@@ -373,6 +373,37 @@ pub fn get_report(c_challenge: Option<&repr_c::Vec>, c_ima: &repr_c::TaggedO - report.into() - } - -+#[cfg(feature = "no_as")] -+use verifier::virtcca_parse_evidence; -+ -+#[cfg(feature = "no_as")] -+#[ffi_export] -+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 { -diff --git a/service/attestation/attestation-service/verifier/src/lib.rs b/service/attestation/attestation-service/verifier/src/lib.rs -index 0b776c2..a0e0b58 100644 ---- a/service/attestation/attestation-service/verifier/src/lib.rs -+++ b/service/attestation/attestation-service/verifier/src/lib.rs -@@ -58,3 +58,10 @@ impl VerifierAPIs for Verifier { - } - } - } -+ -+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/virtcca/mod.rs b/service/attestation/attestation-service/verifier/src/virtcca/mod.rs -index 3de7c9f..ca3a2ff 100644 ---- a/service/attestation/attestation-service/verifier/src/virtcca/mod.rs -+++ b/service/attestation/attestation-service/verifier/src/virtcca/mod.rs -@@ -114,6 +114,16 @@ impl Evidence { - // 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()), --- -2.33.0 - 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/0090-add-no_as-ima-reference-path.patch b/0090-add-no_as-ima-reference-path.patch deleted file mode 100644 index e1cd51939651156c47dc3c393293ba2c5ae3523f..0000000000000000000000000000000000000000 --- a/0090-add-no_as-ima-reference-path.patch +++ /dev/null @@ -1,41 +0,0 @@ -From f5266141477b9ea23c2f674e041d5f8dc6509668 Mon Sep 17 00:00:00 2001 -From: houmingyong -Date: Wed, 16 Oct 2024 19:52:04 +0800 -Subject: [PATCH] add no_as ima reference path - -Conflict: remove attestation/attestation-agent/c_header/example.c ---- - .../attestation-service/verifier/src/virtcca/ima.rs | 8 +++++++- - 1 file changed, 7 insertions(+), 1 deletion(-) - -diff --git a/service/attestation/attestation-service/verifier/src/virtcca/ima.rs b/service/attestation/attestation-service/verifier/src/virtcca/ima.rs -index 30a151f..2b73b46 100644 ---- a/service/attestation/attestation-service/verifier/src/virtcca/ima.rs -+++ b/service/attestation/attestation-service/verifier/src/virtcca/ima.rs -@@ -14,8 +14,13 @@ use ima_measurements::{Event, EventData, Parser}; - use fallible_iterator::FallibleIterator; - use serde_json::{Value, Map, json}; - -+#[cfg(not(feature = "no_as"))] - 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 {} - -@@ -72,7 +77,8 @@ 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(); --- -2.33.0 - diff --git a/0090-add-ra_tls-support.patch b/0090-add-ra_tls-support.patch new file mode 100644 index 0000000000000000000000000000000000000000..1313fec51684e00416f90c03432ad4ae4d8a5443 --- /dev/null +++ b/0090-add-ra_tls-support.patch @@ -0,0 +1,2054 @@ +From dcb66bb5945fdb61aaa42799a8a13899a92f04ff Mon Sep 17 00:00:00 2001 +From: xuraoqing +Date: Tue, 18 Feb 2025 11:59:35 +0800 +Subject: [PATCH] add ra_tls support + +Signed-off-by: xuraoqing +--- + component/CMakeLists.txt | 1 + + component/ra_tls/CMakeLists.txt | 43 ++ + component/ra_tls/LICENSE | 194 +++++++ + component/ra_tls/README.md | 3 + + component/ra_tls/openssl_imp.c | 669 ++++++++++++++++++++++++ + component/ra_tls/ra_tls.c | 898 ++++++++++++++++++++++++++++++++ + component/ra_tls/ra_tls.h | 65 +++ + component/ra_tls/ra_tls_imp.h | 97 ++++ + 8 files changed, 1970 insertions(+) + create mode 100644 component/ra_tls/CMakeLists.txt + create mode 100644 component/ra_tls/LICENSE + create mode 100644 component/ra_tls/README.md + create mode 100644 component/ra_tls/openssl_imp.c + create mode 100644 component/ra_tls/ra_tls.c + create mode 100644 component/ra_tls/ra_tls.h + create mode 100644 component/ra_tls/ra_tls_imp.h + +diff --git a/component/CMakeLists.txt b/component/CMakeLists.txt +index 83aa4cd1..4d917455 100644 +--- a/component/CMakeLists.txt ++++ b/component/CMakeLists.txt +@@ -14,6 +14,7 @@ ADD_SUBDIRECTORY(secure_channel) + + ADD_SUBDIRECTORY(remote_attest) + ADD_SUBDIRECTORY(local_attest) ++ADD_SUBDIRECTORY(ra_tls) + + + +diff --git a/component/ra_tls/CMakeLists.txt b/component/ra_tls/CMakeLists.txt +new file mode 100644 +index 00000000..c46a3ee4 +--- /dev/null ++++ b/component/ra_tls/CMakeLists.txt +@@ -0,0 +1,43 @@ ++# 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. ++ ++cmake_minimum_required(VERSION 3.10 FATAL_ERROR) ++project(ra_tls VERSION 0.1) ++ ++set(LIB_NAME ra_tls) ++set(LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/ra_tls.c) ++set(LD_SO cjson curl) ++set(HEADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/ra_tls.h ${CMAKE_CURRENT_SOURCE_DIR}/ra_tls_imp.h) ++if (NOT TLS_LIB) ++set(TLS_LIB OPENSSL) ++endif() ++if (CMAKE_BUILD_TYPE MATCHES Debug) ++add_definitions(-DDEBUG) ++endif() ++if (TLS_LIB MATCHES OPENSSL) ++ add_definitions(-DUSE_OPENSSL) ++ set(LD_SO ${LD_SO} crypto ssl) ++ set(LIB_SRC ${LIB_SRC} ${CMAKE_CURRENT_SOURCE_DIR}/openssl_imp.c) ++else() ++ message(FATAL_ERROR "TLS_LIB should defined") ++endif() ++ ++FILE(GLOB_RECURSE BASE64_SRC CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/../../thirdparty/base64url/*.c") ++include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../thirdparty/base64url) ++ ++set(LIB_SRC ${LIB_SRC} ${BASE64_SRC}) ++add_library(${LIB_NAME} SHARED ${LIB_SRC}) ++target_link_libraries(${LIB_NAME} PUBLIC ${LD_SO}) ++ ++set_target_properties(${LIB_NAME} PROPERTIES PUBLIC_HEADER "${HEADER_FILES}") ++install(TARGETS ${LIB_NAME} ++ LIBRARY DESTINATION ${LOCAL_ROOT_PATH_INSTALL}/usr/lib64 ++ PUBLIC_HEADER DESTINATION ${LOCAL_ROOT_PATH_INSTALL}/usr/include/secGear ++ PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) +\ No newline at end of file +diff --git a/component/ra_tls/LICENSE b/component/ra_tls/LICENSE +new file mode 100644 +index 00000000..f63f5a9c +--- /dev/null ++++ b/component/ra_tls/LICENSE +@@ -0,0 +1,194 @@ ++木兰宽松许可证,第2版 ++ ++木兰宽松许可证,第2版 ++ ++2020年1月 http://license.coscl.org.cn/MulanPSL2 ++ ++您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束: ++ ++0. 定义 ++ ++“软件” 是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。 ++ ++“贡献” 是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。 ++ ++“贡献者” 是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 ++ ++“法人实体” 是指提交贡献的机构及其“关联实体”。 ++ ++“关联实体” 是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是 ++指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 ++ ++1. 授予版权许可 ++ ++每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可 ++以复制、使用、修改、分发其“贡献”,不论修改与否。 ++ ++2. 授予专利许可 ++ ++每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定 ++撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡 ++献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软 ++件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“ ++关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或 ++其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权 ++行动之日终止。 ++ ++3. 无商标许可 ++ ++“本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定 ++的声明义务而必须使用除外。 ++ ++4. 分发限制 ++ ++您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“ ++本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。 ++ ++5. 免责声明与责任限制 ++ ++“软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对 ++任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于 ++何种法律理论,即使其曾被建议有此种损失的可能性。 ++ ++6. 语言 ++ ++“本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文 ++版为准。 ++ ++条款结束 ++ ++如何将木兰宽松许可证,第2版,应用到您的软件 ++ ++如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步: ++ ++1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; ++ ++2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; ++ ++3, 请将如下声明文本放入每个源文件的头部注释中。 ++ ++Copyright (c) [Year] [name of copyright holder] ++[Software Name] is licensed under 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. ++ ++Mulan Permissive Software License,Version 2 ++ ++Mulan Permissive Software License,Version 2 (Mulan PSL v2) ++ ++January 2020 http://license.coscl.org.cn/MulanPSL2 ++ ++Your reproduction, use, modification and distribution of the Software shall ++be subject to Mulan PSL v2 (this License) with the following terms and ++conditions: ++ ++0. Definition ++ ++Software means the program and related documents which are licensed under ++this License and comprise all Contribution(s). ++ ++Contribution means the copyrightable work licensed by a particular ++Contributor under this License. ++ ++Contributor means the Individual or Legal Entity who licenses its ++copyrightable work under this License. ++ ++Legal Entity means the entity making a Contribution and all its ++Affiliates. ++ ++Affiliates means entities that control, are controlled by, or are under ++common control with the acting entity under this License, ‘control’ means ++direct or indirect ownership of at least fifty percent (50%) of the voting ++power, capital or other securities of controlled or commonly controlled ++entity. ++ ++1. Grant of Copyright License ++ ++Subject to the terms and conditions of this License, each Contributor hereby ++grants to you a perpetual, worldwide, royalty-free, non-exclusive, ++irrevocable copyright license to reproduce, use, modify, or distribute its ++Contribution, with modification or not. ++ ++2. Grant of Patent License ++ ++Subject to the terms and conditions of this License, each Contributor hereby ++grants to you a perpetual, worldwide, royalty-free, non-exclusive, ++irrevocable (except for revocation under this Section) patent license to ++make, have made, use, offer for sale, sell, import or otherwise transfer its ++Contribution, where such patent license is only limited to the patent claims ++owned or controlled by such Contributor now or in future which will be ++necessarily infringed by its Contribution alone, or by combination of the ++Contribution with the Software to which the Contribution was contributed. ++The patent license shall not apply to any modification of the Contribution, ++and any other combination which includes the Contribution. If you or your ++Affiliates directly or indirectly institute patent litigation (including a ++cross claim or counterclaim in a litigation) or other patent enforcement ++activities against any individual or entity by alleging that the Software or ++any Contribution in it infringes patents, then any patent license granted to ++you under this License for the Software shall terminate as of the date such ++litigation or activity is filed or taken. ++ ++3. No Trademark License ++ ++No trademark license is granted to use the trade names, trademarks, service ++marks, or product names of Contributor, except as required to fulfill notice ++requirements in section 4. ++ ++4. Distribution Restriction ++ ++You may distribute the Software in any medium with or without modification, ++whether in source or executable forms, provided that you provide recipients ++with a copy of this License and retain copyright, patent, trademark and ++disclaimer statements in the Software. ++ ++5. Disclaimer of Warranty and Limitation of Liability ++ ++THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY ++KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR ++COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT ++LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING ++FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO ++MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF ++THE POSSIBILITY OF SUCH DAMAGES. ++ ++6. Language ++ ++THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION ++AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF ++DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION ++SHALL PREVAIL. ++ ++END OF THE TERMS AND CONDITIONS ++ ++How to Apply the Mulan Permissive Software License,Version 2 ++(Mulan PSL v2) to Your Software ++ ++To apply the Mulan PSL v2 to your work, for easy identification by ++recipients, you are suggested to complete following three steps: ++ ++i. Fill in the blanks in following statement, including insert your software ++name, the year of the first publication of your software, and your name ++identified as the copyright owner; ++ ++ii. Create a file named "LICENSE" which contains the whole context of this ++License in the first directory of your software package; ++ ++iii. Attach the statement to the appropriate annotated syntax at the ++beginning of each source file. ++ ++Copyright (c) [Year] [name of copyright holder] ++[Software Name] is licensed under 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. +diff --git a/component/ra_tls/README.md b/component/ra_tls/README.md +new file mode 100644 +index 00000000..f926477f +--- /dev/null ++++ b/component/ra_tls/README.md +@@ -0,0 +1,3 @@ ++# ra-tls ++ ++secGear支持ra-tls +\ No newline at end of file +diff --git a/component/ra_tls/openssl_imp.c b/component/ra_tls/openssl_imp.c +new file mode 100644 +index 00000000..82e0fd0a +--- /dev/null ++++ b/component/ra_tls/openssl_imp.c +@@ -0,0 +1,669 @@ ++/* ++ * 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. ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "ra_tls_imp.h" ++ ++#define OID_LEN_MAX 64 ++#define CERT_TIME_STR_LEN 17 ++#define RSA_PRIVATE_KEY_BITS_2048 2048 ++#define RSA_PRIVATE_KEY_BITS_3072 3072 ++#define RSA_PRIVATE_KEY_BUF_2048 2048 ++#define RSA_PRIVATE_KEY_BUF_3072 3072 ++#define RSA_PUB_KEY_BUF_2048 512 ++#define RSA_PUB_KEY_BUF_3072 650 ++ ++#define ERR_CHECK(CONDITION, RESULT, ERRNO, GOTO_ERR, PREFIX) do { \ ++ if ((CONDITION)) { \ ++ RESULT = ERRNO; \ ++ printf("%s:%d, %s: %s", __FILE__, __LINE__, PREFIX, ERR_reason_error_string(ERR_get_error())); \ ++ goto GOTO_ERR; \ ++ } \ ++} while (0) ++ ++const size_t MIN_CERTIFICATE_SIZE = 4096; ++ ++int ra_tls_buf_init(ra_tls_buf *buf, int len) ++{ ++ if (buf == NULL || len < 0) { ++ return -1; ++ } ++ if (len == 0) { ++ buf->buf = NULL; ++ buf->len = 0; ++ buf->filled = 0; ++ return 0; ++ } ++ buf->buf = malloc(len); ++ if (buf->buf == NULL) { ++ return -1; ++ } ++ memset(buf->buf, 0, len); ++ buf->len = len; ++ buf->filled = 0; ++ return len; ++} ++ ++void ra_tls_buf_free(ra_tls_buf *buf) ++{ ++ if (buf == NULL) { ++ return; ++ } ++ if (buf->buf) { ++ free(buf->buf); ++ buf->buf = NULL; ++ } ++ buf->len = 0; ++ buf->filled = 0; ++} ++ ++static int generate_pkey_rsa(EVP_PKEY *pk, key_size key_len) ++{ ++ int ret = -1; ++ int key_bits = 0; ++ if (pk == NULL) { ++ return ret; ++ } ++ EVP_PKEY_CTX *ctx = NULL; ++ if (key_len == RSA_2048) { ++ key_bits = RSA_PRIVATE_KEY_BITS_2048; ++ } else if (key_len == RSA_3072) { ++ key_bits = RSA_PRIVATE_KEY_BITS_3072; ++ } else { ++ printf("unknown key length:%d\n", key_len); ++ return ret; ++ } ++ ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); ++ if (ctx == NULL) { ++ return ret; ++ } ++ int res = EVP_PKEY_keygen_init(ctx); ++ if (res <= 0) { ++ printf("key generate failed (%d)\n", res); ++ ret = -1; ++ goto done; ++ } ++ ++ res = EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, key_bits); ++ if (res <= 0) { ++ printf("set_rsa_kengen_bits failed (%d)\n", res); ++ ret = -1; ++ goto done; ++ } ++ res = EVP_PKEY_keygen(ctx, &pk); ++ if (res <= 0) { ++ printf("keygen failed (%d)\n", res); ++ ret = -1; ++ goto done; ++ } ++ ret = 0; ++done: ++ if (ctx) { ++ EVP_PKEY_CTX_free(ctx); ++ } ++ ++ return ret; ++} ++ ++// type = 0, 公钥,=1 私钥 ++static int read_key(EVP_PKEY *pkey, key_type type, ra_tls_buf *key) ++{ ++ BIO *bio = NULL; ++ int ret = -1; ++ int res; ++ if (pkey == NULL) { ++ return ret; ++ } ++ bio = BIO_new(BIO_s_mem()); ++ if (bio == NULL) { ++ printf("bio new failed"); ++ return ret; ++ } ++ if (type == KEY_PUBLIC) { ++ ret = i2d_PUBKEY_bio(bio, pkey); ++ } else if (type == KEY_PRIVATE) { ++ ret = i2d_PrivateKey_bio(bio, pkey); ++ } else { ++ printf("unknown key type\n"); ++ ret = -1; ++ } ++ res = BIO_read(bio, key->buf, key->len); ++ if (res > 0) { ++ ret = 0; ++ key->filled = res; ++ } ++ if (bio) { ++ BIO_free(bio); ++ } ++ bio = NULL; ++ return ret; ++} ++ ++static X509_NAME *create_x509_name(const char *country_name, const char *org, const char *comm_name) ++{ ++ X509_NAME* name = X509_NAME_new(); ++ X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (const unsigned char *)country_name, -1, -1, 0); ++ X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (const unsigned char *)org, -1, -1, 0); ++ X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (const unsigned char *)comm_name, -1, -1, 0); ++ return name; ++} ++ ++static int set_public_key(X509 *cert, ra_tls_buf *pub_key) ++{ ++ int ret = -1; ++ int res = 0; ++ EVP_PKEY *pkey = NULL; ++ BIO *bio = NULL; ++ pkey = EVP_PKEY_new(); ++ ERR_CHECK(pkey == NULL, ret, -1, err, "new public key error"); ++ bio = BIO_new_mem_buf((const void*)pub_key->buf, (int)pub_key->filled); ++ ERR_CHECK(bio == NULL, ret, -1, err, "bio new error"); ++ EVP_PKEY *key = d2i_PUBKEY_bio(bio, &pkey); ++ ERR_CHECK(key == NULL, ret, -1, err, "read public key error"); ++ res = X509_set_pubkey(cert, pkey); ++ ERR_CHECK(res == 0, ret, -1, err, "set x509 public key error"); ++ ret = 0; ++err: ++ if (bio) { ++ BIO_free(bio); ++ } ++ if (pkey) { ++ EVP_PKEY_free(pkey); ++ } ++ return ret; ++} ++ ++static int sign_cert(X509 *cert, ra_tls_buf *prv_key) ++{ ++ int ret = -1; ++ int res = 0; ++ EVP_PKEY *pkey = NULL; ++ BIO *bio = NULL; ++ pkey = EVP_PKEY_new(); ++ ERR_CHECK(pkey == NULL, ret, -1, err, "new private key error"); ++ bio = BIO_new_mem_buf((const void*)prv_key->buf, (int)prv_key->filled); ++ ERR_CHECK(bio == NULL, ret, -1, err, "bio new error"); ++ EVP_PKEY *key = d2i_PrivateKey_bio(bio, &pkey); ++ ERR_CHECK(key == NULL, ret, -1, err, "read private key error"); ++ res = X509_sign(cert, pkey, EVP_sha256()); ++ ERR_CHECK(res == 0, ret, -1, err, "sign error"); ++ ret = 0; ++err: ++ if (bio) { ++ BIO_free(bio); ++ } ++ if (pkey) { ++ EVP_PKEY_free(pkey); ++ } ++ return ret; ++} ++ ++static int set_subject(X509 *cert) ++{ ++ int ret = -1; ++ int res = 0; ++ X509_NAME *name = NULL; ++ name = create_x509_name("ZH", "Huawei Corporation", "VirtCCA Enclave"); ++ ERR_CHECK(name == NULL, ret, -1, err, "create subject name error"); ++ res = X509_set_subject_name(cert, name); ++ ERR_CHECK(res == 0, ret, -1, err, "set x509 subject name error"); ++ ret = 0; ++err: ++ if (name) { ++ X509_NAME_free(name); ++ } ++ return ret; ++} ++ ++static int set_issuer(X509 *cert) ++{ ++ int ret = -1; ++ int res = 0; ++ X509_NAME *name = NULL; ++ name = create_x509_name("ZH", "Huawei Corporation", "VirtCCA Enclave"); ++ ERR_CHECK(name == NULL, ret, -1, err, "create issuer name error"); ++ res = X509_set_issuer_name(cert, name); ++ ERR_CHECK(res == 0, ret, -1, err, "set x509 issuer name error"); ++ ret = 0; ++err: ++ if (name) { ++ X509_NAME_free(name); ++ } ++ return ret; ++} ++ ++static int set_extension(X509 *cert, cert_config *cfg) ++{ ++ int res = 0; ++ int ret = -1; ++ X509V3_CTX ctx; ++ ASN1_OBJECT *obj = NULL; ++ ASN1_OCTET_STRING *data = NULL; ++ X509_EXTENSION *ext = NULL; ++ ++ X509V3_set_ctx_nodb(&ctx); ++ X509V3_set_ctx(&ctx, cert, cert, NULL, NULL, 0); ++ ++ // set extension ++ ext = X509V3_EXT_conf_nid(NULL, &ctx, NID_basic_constraints, "CA:FALSE"); ++ res = X509_add_ext(cert, ext, -1); ++ ERR_CHECK(res == 0, ret, -1, err, "x509 add basic constraints error"); ++ X509_EXTENSION_free(ext); ++ ext = NULL; ++ ++ ext = X509V3_EXT_conf_nid(NULL, &ctx, NID_subject_key_identifier, "hash"); ++ res = X509_add_ext(cert, ext, -1); ++ ERR_CHECK(res == 0, ret, -1, err, "x509 add subject key identifier error"); ++ X509_EXTENSION_free(ext); ++ ext = NULL; ++ ++ ext = X509V3_EXT_conf_nid(NULL, &ctx, NID_authority_key_identifier, "keyid:always"); ++ res = X509_add_ext(cert, ext, -1); ++ ERR_CHECK(res == 0, ret, -1, err, "x509 add authority key identifier error"); ++ X509_EXTENSION_free(ext); ++ ext = NULL; ++ ++ // add extension evidence ++ obj = OBJ_txt2obj(cfg->ext_oid, 1); ++ ERR_CHECK(obj == NULL, ret, -1, err, "create asn1 obj error"); ++ data = ASN1_OCTET_STRING_new(); ++ ERR_CHECK(data == NULL, ret, -1, err, "create asn1 string error"); ++ res = ASN1_OCTET_STRING_set(data, cfg->ext.buf, strlen((const char *)cfg->ext.buf)); ++ ERR_CHECK(res == 0, ret, -1, err, "asn1 set string error"); ++ ext = X509_EXTENSION_create_by_OBJ(&ext, obj, 0, data); ++ ERR_CHECK(ext == NULL, ret, -1, err, "create x509 extension error"); ++ res = X509_add_ext(cert, ext, -1); ++ ERR_CHECK(res == 0, ret, -1, err, "x509 add evidence error"); ++ ret = 0; ++err: ++ if (ext) { ++ X509_EXTENSION_free(ext); ++ } ++ if (data) { ++ ASN1_OCTET_STRING_free(data); ++ } ++ if (obj) { ++ ASN1_OBJECT_free(obj); ++ } ++ return ret; ++} ++ ++static int output_certificate_der(ra_tls_buf *output, X509 *cert) ++{ ++ int ret = -1; ++ int res = 0; ++ BIO *bio = NULL; ++ bio = BIO_new(BIO_s_mem()); ++ ERR_CHECK(bio == NULL, ret, -1, err, "bio new failed"); ++ ERR_clear_error(); ++ res = i2d_X509_bio(bio, cert); ++ ERR_CHECK(res == 0, ret, -1, err, "output certificate to bio error"); ++ res = BIO_read(bio, output->buf, output->len); ++ ERR_CHECK(res <= 0, ret, -1, err, "read cert from bio error"); ++ output->filled = res; ++ ret = 0; ++ goto end; ++err: ++ ra_tls_buf_free(output); ++end: ++ if (bio) { ++ BIO_free(bio); ++ } ++ return ret; ++} ++ ++static int generate_x509_self_signed_certificate(ra_tls_buf *output_cert, cert_config *cfg) ++{ ++ int res = 0; ++ int ret = -1; ++ const int x509_ver = 2; // 2 means X509 Version 3 ++ X509 *x509cert = NULL; ++ if (output_cert == NULL || cfg == NULL) { ++ return ret; ++ } ++ ERR_load_crypto_strings(); ++ OPENSSL_init_crypto(0, NULL); ++ // certificate version 3 ++ x509cert = X509_new(); ++ res = X509_set_version(x509cert, x509_ver); ++ ERR_CHECK(res == 0, ret, -1, err, "set x509 version error"); ++ ret = set_public_key(x509cert, &(cfg->pub_key)); ++ ERR_CHECK(res < 0, ret, -1, err, "set x509 public key error"); ++ res = set_subject(x509cert); ++ ERR_CHECK(res < 0, ret, -1, err, "set x509 subject name error"); ++ res = set_issuer(x509cert); ++ ERR_CHECK(res < 0, ret, -1, err, "set x509 issuer name error"); ++ ++ // set serial number ++ res = ASN1_INTEGER_set(X509_get_serialNumber(x509cert), 1); ++ ERR_CHECK(res == 0, ret, -1, err, "set x509 serial number error"); ++ ++ // set date ++ res = ASN1_TIME_set_string(X509_getm_notBefore(x509cert), cfg->not_before); ++ ERR_CHECK(res == 0, ret, -1, err, "set x509 not before error"); ++ res = ASN1_TIME_set_string(X509_getm_notAfter(x509cert), cfg->not_after); ++ ERR_CHECK(res == 0, ret, -1, err, "set x509 not after error"); ++ ++ // set extension ++ res = set_extension(x509cert, cfg); ++ ERR_CHECK(res < 0, ret, -1, err, "set x509 extension error"); ++ // sign certificate ++ res = sign_cert(x509cert, &(cfg->prv_key)); ++ ERR_CHECK(res < 0, ret, -1, err, "sign error"); ++ // output certificate ++ res = ra_tls_buf_init(output_cert, cfg->pub_key.len + cfg->ext.len + MIN_CERTIFICATE_SIZE); ++ ERR_CHECK(res <= 0, ret, -1, err, "init buffer failed"); ++ res = output_certificate_der(output_cert, x509cert); ++ ERR_CHECK(res < 0, ret, -1, err, "output certificate failed"); ++ ret = 0; ++err: ++ if (x509cert) { ++ X509_free(x509cert); ++ } ++ return ret; ++} ++ ++static int openssl_generate_certificate_with_extension(ra_tls_buf *cert, ra_tls_buf *ext, ++ ra_tls_buf *public_key, ra_tls_buf *private_key, const char *oid) ++{ ++ int res = 0; ++ int ret = -1; ++ char not_before[CERT_TIME_STR_LEN] = {0}; ++ char not_after[CERT_TIME_STR_LEN] = {0}; ++ // fill config and generate certificate ++ if (cert == NULL || ext == NULL || public_key == NULL || private_key == NULL || oid == NULL) { ++ return ret; ++ } ++ time_t now = time(NULL); ++ struct tm *tm = localtime(&now); ++ ++ res = strftime(not_before, CERT_TIME_STR_LEN, "%Y%m%d%H%M%SZ", tm); ++ if (!res) { ++ return ret; ++ } ++ tm->tm_year += DEFAULT_CERT_LIFETIME_YEARS; ++ res = strftime(not_after, CERT_TIME_STR_LEN, "%Y%m%d%H%M%SZ", tm); ++ if (!res) { ++ return ret; ++ } ++ cert_config cert_cfg; ++ cert_cfg.prv_key = *private_key; ++ cert_cfg.pub_key = *public_key; ++ cert_cfg.not_before = not_before; ++ cert_cfg.not_after = not_after; ++ cert_cfg.ext_oid = oid; ++ cert_cfg.ext = *ext; ++ res = generate_x509_self_signed_certificate(cert, &cert_cfg); ++ if (res >= 0) { ++ ret = 0; ++ } ++ return ret; ++} ++ ++static int init_key_buffer(key_size key_len, ra_tls_buf *public_key, ra_tls_buf *private_key) ++{ ++ int ret = -1; ++ int pub_len = 0; ++ int prv_len = 0; ++ if (key_len == RSA_2048) { ++ pub_len = RSA_PUB_KEY_BUF_2048; ++ prv_len = RSA_PRIVATE_KEY_BUF_2048; ++ } else if (key_len == RSA_3072) { ++ pub_len = RSA_PUB_KEY_BUF_3072; ++ prv_len = RSA_PRIVATE_KEY_BUF_3072; ++ } else { ++ printf("unknown key length\n"); ++ return -1; ++ } ++ if (ra_tls_buf_init(public_key, pub_len) < 0) { ++ ret = -1; ++ goto err; ++ } ++ if (ra_tls_buf_init(private_key, prv_len) < 0) { ++ ret = -1; ++ goto err; ++ } ++ ret = 0; ++ return ret; ++err: ++ ra_tls_buf_free(public_key); ++ ra_tls_buf_free(private_key); ++ return ret; ++} ++ ++static int get_sha256(ra_tls_buf* hash, ra_tls_buf* input) ++{ ++ int res = 0; ++ int ret = -1; ++ unsigned int hash_len; ++ EVP_MD_CTX *md_ctx; ++ if (hash == NULL || input == NULL) { ++ return ret; ++ } ++ md_ctx = EVP_MD_CTX_new(); ++ if (EVP_DigestInit_ex(md_ctx, EVP_sha256(), NULL) != 1) { ++ goto err; ++ } ++ EVP_DigestUpdate(md_ctx, input->buf, input->filled); ++ ra_tls_buf_init(hash, EVP_MAX_MD_SIZE); ++ hash_len = EVP_MAX_MD_SIZE; ++ res = EVP_DigestFinal_ex(md_ctx, hash->buf, &hash_len); ++ if (res <= 0) { ++ ret = -1; ++ goto err; ++ } ++ hash->filled = hash_len; ++ ret = 0; ++ goto end; ++err: ++ ra_tls_buf_free(hash); ++end: ++ if (md_ctx) { ++ EVP_MD_CTX_free(md_ctx); ++ } ++ return ret; ++} ++ ++int generate_key_pair_der(key_size key_len, ra_tls_buf *public_key, ra_tls_buf *private_key) ++{ ++ int res; ++ int ret = -1; ++ EVP_PKEY *pkey = NULL; ++ ra_tls_buf pub_key = RA_TLS_BUF_INIT; ++ ra_tls_buf prv_key = RA_TLS_BUF_INIT; ++ if (public_key == NULL || private_key == NULL) { ++ return ret; ++ } ++ res = init_key_buffer(key_len, &pub_key, &prv_key); ++ if (res < 0) { ++ return -1; ++ } ++ ++ pkey = EVP_PKEY_new(); ++ if (pkey == NULL) { ++ printf("EVP_PKEY_new return NULL\n"); ++ return ret; ++ } ++ res = generate_pkey_rsa(pkey, key_len); ++ if (res < 0) { ++ printf("generate rsa key failed\n"); ++ ret = -1; ++ goto err; ++ } ++ res = read_key(pkey, KEY_PUBLIC, &pub_key); ++ if (res < 0) { ++ printf("read public key failed\n"); ++ ret = -1; ++ goto err; ++ } ++ res = read_key(pkey, KEY_PRIVATE, &prv_key); ++ if (res < 0) { ++ printf("read private key failed\n"); ++ ret = -1; ++ goto err; ++ } ++ *public_key = pub_key; ++ *private_key = prv_key; ++ ret = 0; ++err: ++ if (pkey) { ++ EVP_PKEY_free(pkey); ++ } ++ return ret; ++} ++ ++int get_hash(ra_tls_buf *hash, ra_tls_buf *input, hash_type type) ++{ ++ switch (type) { ++ case SHA_256: ++ get_sha256(hash, input); ++ break; ++ default: ++ printf("unknown hash type\n"); ++ return -1; ++ } ++ return 0; ++} ++ ++int generate_certificate_with_extension(ra_tls_buf *cert, ra_tls_buf *extension, ra_tls_buf *public_key, ++ ra_tls_buf *private_key, const char *oid) ++{ ++ return openssl_generate_certificate_with_extension(cert, extension, public_key, private_key, oid); ++} ++ ++static int get_extension_from_X509(ra_tls_buf *ext_buf, ra_tls_buf *oid, X509 *cert) ++{ ++ int extensions_cnt = 0; ++ if (cert == NULL || ext_buf == NULL || oid == NULL) { ++ return -1; ++ } ++ const STACK_OF(X509_EXTENSION) *extensions = NULL; ++ if (!(extensions = X509_get0_extensions(cert))) { ++ printf("get extensions failed: %s\n", ERR_reason_error_string(ERR_get_error())); ++ return -1; ++ } ++ extensions_cnt = sk_X509_EXTENSION_num(extensions); ++ ++ ra_tls_buf_init(oid, OID_LEN_MAX + 1); ++ for (int i = 0; i < extensions_cnt; i++) { ++ X509_EXTENSION *ext; ++ ASN1_OBJECT *asn1_obj; ++ ASN1_OCTET_STRING *asn1_str; ++ ext = sk_X509_EXTENSION_value(extensions, i); ++ if (ext == NULL) { ++ printf("get extension[%d] failed: %s\n", i, ERR_reason_error_string(ERR_get_error())); ++ goto err; ++ } ++ if (!(asn1_obj = X509_EXTENSION_get_object(ext))) { ++ printf("get extensions obj failed: %s\n", ERR_reason_error_string(ERR_get_error())); ++ goto err; ++ } ++ if (!OBJ_obj2txt((char *)oid->buf, oid->len, asn1_obj, 1)) { ++ printf("get extensions oid failed: %s\n", ERR_reason_error_string(ERR_get_error())); ++ goto err; ++ } ++ if (strcmp((const char *)oid->buf, EVIDENCE_OID) != 0 && strcmp((const char *)oid->buf, TOKEN_OID) != 0) { ++ continue; ++ } ++ if (!(asn1_str = X509_EXTENSION_get_data(ext))) { ++ printf("get extensions data failed: %s\n", ERR_reason_error_string(ERR_get_error())); ++ goto err; ++ } ++ ra_tls_buf_init(ext_buf, asn1_str->length + 1); ++ memcpy(ext_buf->buf, asn1_str->data, asn1_str->length); ++ ext_buf->filled = asn1_str->length; ++ ext_buf->buf[ext_buf->filled] = '\0'; ++#ifdef DEBUG ++ printf("oid: %s\n", oid->buf); ++ printf("extension:\n%s\n", ext_buf->buf); ++#endif ++ return 0; ++ } ++err: ++ ra_tls_buf_free(ext_buf); ++ ra_tls_buf_free(oid); ++ return -1; ++} ++// for verify ++int get_extension_from_certificate_context(ra_tls_buf *ext_buf, ra_tls_buf *oid, void *cert_ctx) ++{ ++ int ret = -1; ++ X509 *cert = NULL; ++ X509_STORE_CTX *x509_ctx = (X509_STORE_CTX *)cert_ctx; ++ if (ext_buf == NULL || oid == NULL) { ++ goto end; ++ } ++ cert = X509_STORE_CTX_get_current_cert(x509_ctx); ++ if (cert == NULL) { ++ printf("get certificate failed: %s\n", ERR_reason_error_string(ERR_get_error())); ++ goto end; ++ } ++ if (get_extension_from_X509(ext_buf, oid, cert) < 0) { ++ goto end; ++ } ++ ret = 0; ++end: ++ return ret; ++} ++ ++int get_public_key_from_certificate_context(ra_tls_buf* key_der, void* cert_ctx) ++{ ++ int ret = -1; ++ X509 *cert = NULL; ++ unsigned char *pub_key = NULL; ++ int pub_key_len = 0; ++ X509_STORE_CTX *x509_ctx = (X509_STORE_CTX*)cert_ctx; ++ cert = X509_STORE_CTX_get_current_cert(x509_ctx); ++ if (cert == NULL) { ++ printf("get certificate failed: %s\n", ERR_reason_error_string(ERR_get_error())); ++ goto err; ++ } ++ ra_tls_buf_init(key_der, KEY_SIZE_MAX); ++ pub_key = key_der->buf; ++ pub_key_len = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(cert), &pub_key); ++ if (pub_key_len <= 0) { ++ printf("get public key failed: %s\n", ERR_reason_error_string(ERR_get_error())); ++ goto err; ++ } ++ key_der->filled = pub_key_len; ++ ret = 0; ++err: ++ return ret; ++} ++ ++int get_extension_from_certificate_der(ra_tls_buf *ext_buf, ra_tls_buf *oid, ra_tls_buf *cert_der) ++{ ++ int ret = -1; ++ BIO *bio_cert = BIO_new_mem_buf(cert_der->buf, cert_der->filled); ++ X509 *icert = NULL; ++ if (d2i_X509_bio(bio_cert, &icert) == NULL) { ++ printf("der read certificate failed: %s\n", ERR_reason_error_string(ERR_get_error())); ++ goto err; ++ } ++ if (get_extension_from_X509(ext_buf, oid, icert) < 0) { ++ goto err; ++ } ++ ret = 0; ++err: ++ if (bio_cert) { ++ BIO_free(bio_cert); ++ } ++ if (icert) { ++ X509_free(icert); ++ } ++ return ret; ++} +diff --git a/component/ra_tls/ra_tls.c b/component/ra_tls/ra_tls.c +new file mode 100644 +index 00000000..eba084fa +--- /dev/null ++++ b/component/ra_tls/ra_tls.c +@@ -0,0 +1,898 @@ ++/* ++ * 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. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include "base64url.h" ++#include "ra_tls.h" ++ ++static char *agent_addr = NULL; ++ ++typedef enum { ++ EVIDENCE, ++ TOKEN ++}extension_type; ++ ++/* internal api */ ++static size_t wb(char *ptr, size_t size, size_t nmemb, void *userdata) ++{ ++ size_t total = size * nmemb; ++ ra_tls_buf *buf = (ra_tls_buf*)userdata; ++ ra_tls_buf_init(buf, total + 1); ++ memcpy(buf->buf, ptr, total); ++ buf->filled = total; ++ buf->buf[buf->filled] = '\0'; ++ return total; ++} ++ ++static int http_request(const char *endpoint, const char *type, const char *data, ra_tls_buf *response) ++{ ++ int ret = -1; ++ CURLcode res; ++ CURL *curl = NULL; ++ struct curl_slist *plist = NULL; ++ curl = curl_easy_init(); ++ if (curl == NULL) { ++ goto err; ++ } ++ plist = curl_slist_append(NULL, "Content-Type:application/json;charset=UTF-8"); ++ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, plist); ++ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, type); ++ if (data) { ++ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data); ++ } ++ curl_easy_setopt(curl, CURLOPT_URL, endpoint); ++ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, wb); ++ curl_easy_setopt(curl, CURLOPT_WRITEDATA, response); ++ curl_easy_setopt(curl, CURLOPT_COOKIEFILE, ""); ++ res = curl_easy_perform(curl); ++ if (res != CURLE_OK) { ++ ret = -1; ++ goto err; ++ } ++ ret = 0; ++ goto end; ++err: ++ ra_tls_buf_free(response); ++end: ++ if (plist) { ++ curl_slist_free_all(plist); ++ } ++ if (curl) { ++ curl_easy_cleanup(curl); ++ } ++ return ret; ++} ++ ++static int get_quote(ra_tls_buf *quote, const char *endpoint_prefix, const char *type, ++ const char *uuid, ra_tls_buf *challenge) ++{ ++ int ret = -1; ++ int res = 0; ++ char *endpoint = NULL; ++ size_t endpoint_len = 0; ++ const char *http_data_format = "{\"challenge\":\"%s\",\"uuid\":\"%s\"}"; ++ char *http_data = NULL; ++ int http_data_len; ++ if (endpoint_prefix == NULL || quote == NULL || uuid == NULL || challenge == NULL) { ++ return -1; ++ } ++ endpoint_len = strlen(endpoint_prefix) + strlen(type) + 1; ++ endpoint = malloc(endpoint_len); ++ if (endpoint == NULL) { ++ goto err; ++ } ++ strcpy(endpoint, endpoint_prefix); ++ strcat(endpoint, type); ++ http_data_len = strlen(uuid) + challenge->filled + strlen(http_data_format) + 1; ++ http_data = malloc(http_data_len); ++ if (http_data == NULL) { ++ ret = -1; ++ goto err; ++ } ++ res = sprintf(http_data, http_data_format, challenge->buf, uuid); ++ ++ res = http_request(endpoint, "GET", http_data, quote); ++ if (res < 0) { ++ goto err; ++ } ++ ret = 0; ++ goto end; ++err: ++ ra_tls_buf_free(quote); ++end: ++ if (http_data) { ++ free(http_data); ++ } ++ if (endpoint) { ++ free(endpoint); ++ } ++ return ret; ++} ++ ++static int get_evidence_ok(const ra_tls_buf *evidence) ++{ ++ int ret = -1; ++ cJSON *json_root = NULL; ++ if (evidence == NULL) { ++ return ret; ++ } ++ if (evidence->filled <= 0) { ++ return ret; ++ } ++ json_root = cJSON_Parse((char*)evidence->buf); ++ if (json_root != NULL) { ++ ret = 0; ++ } ++ cJSON_Delete(json_root); ++ return ret; ++} ++static int get_evidence(ra_tls_buf *evidence, const char *endpoint_prefix, const char *uuid, ra_tls_buf *challenge) ++{ ++ int ret = -1; ++ int res = 0; ++ res = get_quote(evidence, endpoint_prefix, "evidence", uuid, challenge); ++ if (res < 0) { ++ return ret; ++ } ++ res = get_evidence_ok(evidence); ++ if (res < 0) { ++ return ret; ++ } ++ ret = 0; ++ return ret; ++} ++ ++static int get_token_ok(const ra_tls_buf *token) ++{ ++ int ret = -1; ++ size_t offset = 0; ++ cJSON *json_root = NULL; ++ uint8_t *header_base64 = NULL; ++ int header_base64_len = 0; ++ char *header = NULL; ++ size_t header_len = 0; ++ if (token == NULL) { ++ return ret; ++ } ++ if (token->filled <= 0) { ++ return ret; ++ } ++ while (offset < token->filled) { ++ // token sperated by '.' ++ if (token->buf[offset] == '.') { ++ break; ++ } ++ offset++; ++ } ++ if (offset == token->filled) { ++ goto err; ++ } ++ header_base64_len = offset; ++ header_base64 = malloc(header_base64_len + 1); ++ memcpy(header_base64, token->buf, header_base64_len); ++ header_base64[header_base64_len] = '\0'; ++ header = (char*)kpsecl_base64urldecode((const char*)header_base64, header_base64_len, &header_len); ++ if (header_len <= 0) { ++ goto err; ++ } ++ json_root = cJSON_Parse((const char*)header); ++ if (json_root != NULL) { ++ ret = 0; ++ } ++err: ++ if (json_root) { ++ cJSON_Delete(json_root); ++ } ++ if (header_base64) { ++ free(header_base64); ++ } ++ if (header) { ++ free(header); ++ } ++ return ret; ++} ++static int get_token(ra_tls_buf *token, const char *endpoint_prefix, const char *uuid, ra_tls_buf *challenge) ++{ ++ int ret = -1; ++ int res = 0; ++ res = get_quote(token, endpoint_prefix, "token", uuid, challenge); ++ if (res < 0) { ++ return ret; ++ } ++ res = get_token_ok(token); ++ if (res < 0) { ++ return ret; ++ } ++ ret = 0; ++ return ret; ++} ++ ++static char *fill_endpoint(char **endpoint, const char *prefix, const char *source) ++{ ++ size_t endpoint_len = 0; ++ endpoint_len = strlen(prefix) + strlen(source) + 1; ++ *endpoint = malloc(endpoint_len); ++ if (*endpoint == NULL) { ++ return NULL; ++ } ++ strcpy(*endpoint, prefix); ++ strcat(*endpoint, source); ++ return *endpoint; ++} ++ ++static char *fill_http_data(char **data, ra_tls_buf *key_hash) ++{ ++ const char *http_data_format = "{\"user_data\":[]}"; ++ size_t data_len = 0; ++ int res; ++ char *ptr; ++ data_len = strlen(http_data_format) + PUBLIC_KEY_HASH_PRINT_LEN + strlen("]}") + 1; ++ *data = malloc(data_len); ++ if (*data == NULL) { ++ return NULL; ++ } ++ ptr = *data; ++ res = sprintf(ptr, "{\"user_data\":["); ++ ptr += res; ++ for (size_t i = 0; i < key_hash->filled; i++) { ++ res = sprintf(ptr, "%hhu,", key_hash->buf[i]); ++ ptr += res; ++ if (ptr >= *data + data_len) { ++ goto err; ++ } ++ } ++ // point to last character ++ --ptr; ++ if (data_len - (ptr - *data) < 3) { // 3 means min buffer left to filled ++ goto err; ++ } ++ (void)sprintf(ptr, "]}"); ++ goto end; ++err: ++ if (*data) { ++ free(*data); ++ *data = NULL; ++ } ++end: ++ return *data; ++} ++ ++static int get_challenge(ra_tls_buf *challenge, const char *endpoint_prefix, ra_tls_buf *user_data) ++{ ++ int res; ++ int ret = -1; ++ const char *source_name = "challenge"; ++ char *endpoint = NULL; ++ char *http_data = NULL; ++ ra_tls_buf key_hash = RA_TLS_BUF_INIT; ++ ra_tls_buf *pub_key = user_data; ++ if (endpoint_prefix == NULL || challenge == NULL || pub_key == NULL) { ++ return -1; ++ } ++ ra_tls_buf_init(&key_hash, 0); ++ get_hash(&key_hash, pub_key, SHA_256); ++#ifdef DEBUG ++ printf("public key hash:"); ++ for (size_t i = 0; i < key_hash.filled; i++) { ++ printf("%02X", key_hash.buf[i]); ++ } ++ printf("\n"); ++#endif ++ if (fill_endpoint(&endpoint, endpoint_prefix, source_name) == NULL) { ++ goto err; ++ } ++ if (fill_http_data(&http_data, &key_hash) == NULL) { ++ goto err; ++ } ++ res = http_request(endpoint, "GET", http_data, challenge); ++ if (res < 0) { ++ goto err; ++ } ++ ret = 0; ++err: ++ if (endpoint) { ++ free(endpoint); ++ } ++ if (http_data) { ++ free(http_data); ++ } ++ ra_tls_buf_free(&key_hash); ++ return ret; ++} ++ ++static int generate_extension_string(ra_tls_buf *extension, ra_tls_buf *challenge, ra_tls_buf *quote) ++{ ++ int ret = -1; ++ cJSON *ext = NULL; ++ char *json_str = NULL; ++ if (extension == NULL || challenge == NULL || quote == NULL) { ++ return ret; ++ } ++ ext = cJSON_CreateObject(); ++ if (ext == NULL) { ++ return ret; ++ } ++ cJSON_AddStringToObject(ext, "challenge", (char *)challenge->buf); ++ cJSON_AddStringToObject(ext, "quote", (char *)quote->buf); ++ if ((json_str = cJSON_PrintUnformatted(ext)) == NULL) { ++ goto err; ++ } ++ cJSON_Minify(json_str); ++ ra_tls_buf_init(extension, strlen(json_str) + 1); ++ strcpy((char *)extension->buf, json_str); ++ extension->filled = strlen(json_str) + 1; ++ ret = 0; ++err: ++ if (ext) { ++ cJSON_Delete(ext); ++ } ++ if (json_str) { ++ cJSON_free(json_str); ++ } ++ return ret; ++} ++ ++static int get_quote_ra(ra_tls_buf *quote, ra_tls_buf *challenge, ra_tls_buf *pub_key, ra_cfg *cfg) ++{ ++ int ret = -1; ++ int res = 0; ++/* ++ get challenge ++ The public key in the signature certificate needs to be protected. ++ now we use pub_key hash concatenated with challenge. ++*/ ++ res = get_challenge(challenge, cfg->aa_addr, pub_key); ++ if (res < 0) { ++ printf("get challenge failed\n"); ++ goto err; ++ } ++#ifdef DEBUG ++ printf("challenge: %s\n", challenge->buf); ++#endif ++ if (cfg->mode == BACKGROUND) { ++ // get evidence ++ res = get_evidence(quote, cfg->aa_addr, cfg->uuid, challenge); ++ if (res < 0) { ++ printf("get evidence failed\n"); ++ goto err; ++ } ++#ifdef DEBUG ++ printf("evidence: %s\n", quote->buf); ++#endif ++ } else if (cfg->mode == PASSPORT) { ++ // get token ++ res = get_token(quote, cfg->aa_addr, cfg->uuid, challenge); ++ if (res < 0) { ++ printf("get token failed\n"); ++ goto err; ++ } ++#ifdef DEBUG ++ printf("token: %s\n", quote->buf); ++#endif ++ } else { ++ printf("unknown work mode\n"); ++ goto err; ++ } ++ ret = 0; ++err: ++ return ret; ++} ++ ++/* output certificate and private key */ ++int ra_tls_generate_certificate(ra_tls_buf *cert, ra_tls_buf *private_key, ra_cfg *cfg, key_size size) ++{ ++ int res; ++ int ret = -1; ++ const char* oid = NULL; ++ ra_tls_buf pub_key = RA_TLS_BUF_INIT; ++ ra_tls_buf prv_key = RA_TLS_BUF_INIT; ++ ra_tls_buf challenge = RA_TLS_BUF_INIT; ++ ra_tls_buf extension = RA_TLS_BUF_INIT; ++ ra_tls_buf quote = RA_TLS_BUF_INIT; ++ if (cert == NULL || private_key == NULL || cfg == NULL) { ++ return ret; ++ } ++ if (cfg->aa_addr == NULL || cfg->uuid == NULL) { ++ return ret; ++ } ++ if (cfg->aa_addr[strlen(cfg->aa_addr) - 1] != '/') { ++ printf("aa_addr should end with '/'\n"); ++ return ret; ++ } ++ res = generate_key_pair_der(size, &pub_key, &prv_key); ++ if (res < 0) { ++ return ret; ++ } ++ *private_key = prv_key; ++ res = get_quote_ra("e, &challenge, &pub_key, cfg); ++ if (res < 0) { ++ goto err; ++ } ++// extension contained: evidence or token and challenge ++ if (generate_extension_string(&extension, &challenge, "e) < 0) { ++ goto err; ++ } ++#ifdef DEBUG ++ printf("certificate extension: %s\n", extension.buf); ++#endif ++// generate certificate ++ oid = (cfg->mode == BACKGROUND)? EVIDENCE_OID : TOKEN_OID; ++ res = generate_certificate_with_extension(cert, &extension, &pub_key, &prv_key, oid); ++ if (res < 0) { ++ ret = -1; ++ } ++ ret = 0; ++err: ++ ra_tls_buf_free(&pub_key); ++ ra_tls_buf_free(&challenge); ++ ra_tls_buf_free(&extension); ++ ra_tls_buf_free("e); ++ return ret; ++} ++ ++// for client to verify externsion in certificate ++int ra_tls_set_addr(char *addr) ++{ ++ if (addr == NULL) { ++ return -1; ++ } ++ if (addr[strlen(addr) - 1] != '/') { ++ printf("host should end with '/'\n"); ++ return -1; ++ } ++ agent_addr = addr; ++ return 0; ++} ++ ++/* ++ token is a string, separated by '.', like this: ++ HEADER.CLAIM.SIGNATURE ++ HEADER,CLAIM encoded by base64_url ++*/ ++static int parse_claim_from_token(ra_tls_buf *claim, ra_tls_buf *token) ++{ ++ size_t claim_start = 0; ++ size_t claim_end = 0; ++ while (claim_start < token->filled) { ++ if (token->buf[claim_start] != '.') { ++ claim_start++; ++ } else { ++ break; ++ } ++ } ++ claim_start++; ++ claim_end = claim_start; ++ while (claim_end < token->filled) { ++ if (token->buf[claim_end] != '.') { ++ claim_end++; ++ } else { ++ break; ++ } ++ } ++ claim_end--; ++ if (claim_end <= claim_start) { ++ return -1; ++ } ++ ra_tls_buf_init(claim, claim_end - claim_start + 1); ++ (void)memcpy(claim->buf, &token->buf[claim_start], claim_end - claim_start + 1); ++ claim->filled = claim_end - claim_start + 1; ++ return 0; ++} ++ ++static int expect_response_true(ra_tls_buf *resp, extension_type mode) ++{ ++ int ret = -1; ++ cJSON *root = NULL; ++ cJSON *obj_parse = NULL; ++ cJSON *obj_get = NULL; ++ if (resp == NULL || resp->buf == NULL) { ++ return -1; ++ } ++ if (mode == EVIDENCE) { ++ root = cJSON_Parse((const char *)resp->buf); ++ cJSON *obj_get = cJSON_GetObjectItemCaseSensitive(root, "evaluation_reports"); ++ if (obj_get == NULL) { ++ goto err; ++ } ++ if (NULL == (obj_get = cJSON_GetObjectItemCaseSensitive(obj_get, "eval_result"))) { ++ goto err; ++ } ++ if (cJSON_IsTrue(obj_get)) { ++ ret = 0; ++ } ++ } else { ++ root = cJSON_Parse((const char*)resp->buf); ++ obj_get = cJSON_GetObjectItemCaseSensitive(root, "claim"); ++ if (obj_get == NULL) { ++ goto err; ++ } ++ char *str = cJSON_GetStringValue(obj_get); ++ if (str == NULL) { ++ goto err; ++ } ++ cJSON* obj_parse = cJSON_Parse(str); ++ cJSON* obj_get = cJSON_GetObjectItemCaseSensitive(obj_parse, "evaluation_reports"); ++ if (obj_get == NULL) { ++ goto err; ++ } ++ if (NULL == (obj_get = cJSON_GetObjectItemCaseSensitive(obj_get, "eval_result"))) { ++ goto err; ++ } ++ if (cJSON_IsTrue(obj_get)) { ++ ret = 0; ++ } ++ } ++err: ++ if (root) { ++ cJSON_Delete(root); ++ } ++ if (obj_parse) { ++ cJSON_Delete(obj_parse); ++ } ++ return ret; ++} ++// extension like this:"{"challenge":"base64_url string","quote":"token or evidence"}" ++static int parse_challenge_from_extension(ra_tls_buf *challenge, ra_tls_buf *ext) ++{ ++ int ret = -1; ++ cJSON *json_root = NULL; ++ cJSON *obj; ++ char *str = NULL; ++ if (challenge == NULL || ext == NULL) { ++ return ret; ++ } ++ if (NULL == (json_root = cJSON_Parse((const char*)ext->buf))) { ++ goto err; ++ } ++ if (NULL == (obj = cJSON_GetObjectItemCaseSensitive(json_root, "challenge"))) { ++ goto err; ++ } ++ if (NULL == (str = cJSON_GetStringValue(obj))) { ++ goto err; ++ } ++ ra_tls_buf_init(challenge, strlen(str) + 1); ++ strcpy((char *)challenge->buf, str); ++ challenge->filled = strlen(str) + 1; ++ ret = 0; ++err: ++ if (json_root) { ++ cJSON_Delete(json_root); ++ } ++ return ret; ++} ++ ++static int parse_quote_from_extension(ra_tls_buf *quote, ra_tls_buf *ext) ++{ ++ int ret = -1; ++ cJSON *json_root = NULL; ++ cJSON *obj; ++ char *str = NULL; ++ if (quote == NULL || ext == NULL) { ++ return ret; ++ } ++ if (NULL == (json_root = cJSON_Parse((const char *)ext->buf))) { ++ goto err; ++ } ++ if (NULL == (obj = cJSON_GetObjectItemCaseSensitive(json_root, "quote"))) { ++ goto err; ++ } ++ if (NULL == (str = cJSON_GetStringValue(obj))) { ++ goto err; ++ } ++ ra_tls_buf_init(quote, strlen(str) + 1); ++ strcpy((char *)quote->buf, str); ++ quote->filled = strlen(str) + 1; ++ ret = 0; ++err: ++ if (json_root) { ++ cJSON_Delete(json_root); ++ } ++ return ret; ++} ++ ++static char *generate_ra_http_data(extension_type type, char **http_data, ra_tls_buf *quote, ra_tls_buf *challenge) ++{ ++ if (type == EVIDENCE) { ++ cJSON* obj = cJSON_CreateObject(); ++ cJSON_AddStringToObject(obj, "evidence", (const char *)quote->buf); ++ cJSON_AddStringToObject(obj, "challenge", (const char *)challenge->buf); ++ *http_data = cJSON_PrintUnformatted(obj); ++ cJSON_Minify(*http_data); ++ cJSON_Delete(obj); ++ } else { ++ cJSON* obj = cJSON_CreateObject(); ++ cJSON_AddStringToObject(obj, "token", (const char *)quote->buf); ++ *http_data = cJSON_PrintUnformatted(obj); ++ cJSON_Minify(*http_data); ++ cJSON_Delete(obj); ++ } ++ return *http_data; ++} ++ ++static int verify_extension_ra(extension_type type, ra_tls_buf *quote, ra_tls_buf *challenge) ++{ ++ int ret = -1; ++ int res = 0; ++ ra_tls_buf response = RA_TLS_BUF_INIT; ++ const char *source_name = NULL; ++ char *endpoint = NULL; ++ size_t endpoint_len = 0; ++ char *http_data = NULL; ++ ++ if (type == EVIDENCE) { ++ source_name = "evidence"; ++ } else if (type == TOKEN) { ++ source_name = "token"; ++ } else { ++ printf("unkonwn extension type\n"); ++ goto err; ++ } ++ endpoint_len = strlen(agent_addr) + strlen(source_name) + 1; ++ endpoint = malloc(endpoint_len); ++ if (endpoint == NULL) { ++ goto err; ++ } ++ strcpy(endpoint, agent_addr); ++ strcat(endpoint, source_name); ++ generate_ra_http_data(type, &http_data, quote, challenge); ++#ifdef DEBUG ++ printf("http request\n"); ++ printf("endpoint: %s\n", endpoint); ++ printf("http data: %s\n", http_data); ++#endif ++ res = http_request(endpoint, "POST", http_data, &response); ++ if (res < 0) { ++ goto err; ++ } ++#ifdef DEBUG ++ printf("response: %s\n", response.buf); ++#endif ++ // check as service response ++ if (expect_response_true(&response, type) != 0) { ++ printf("expect evaluation_reports.eval_result = true, but false or not exist\n"); ++ goto err; ++ } ++ ret = 0; ++err: ++ if (endpoint) { ++ free(endpoint); ++ } ++ if (http_data) { ++ cJSON_free(http_data); ++ } ++ ra_tls_buf_free(&response); ++ return ret; ++} ++ ++static int verify_extension(ra_tls_buf *ext, extension_type type) ++{ ++ int ret = -1; ++ int res; ++ ra_tls_buf challenge = RA_TLS_BUF_INIT; ++ ra_tls_buf quote = RA_TLS_BUF_INIT; ++ ++ if (agent_addr == NULL || ext == NULL) { ++ return ret; ++ } ++ res = parse_challenge_from_extension(&challenge, ext); ++ if (res < 0) { ++ goto err; ++ } ++ res = parse_quote_from_extension("e, ext); ++ if (res < 0) { ++ goto err; ++ } ++ res = verify_extension_ra(type, "e, &challenge); ++ if (res != 0) { ++ ret = -1; ++ } else { ++ ret = 0; ++ } ++err: ++ ra_tls_buf_free(&challenge); ++ ra_tls_buf_free("e); ++ return ret; ++} ++ ++// challenge :last 32 bytes in base64_url_decode(challenge) is the public key hash ++static int get_public_key_hash(ra_tls_buf *key_hash, ra_tls_buf *ext) ++{ ++ int ret = -1; ++ int res; ++ ra_tls_buf challenge_base64 = RA_TLS_BUF_INIT; ++ uint8_t* challenge = NULL; ++ size_t challenge_len = 0; ++ res = parse_challenge_from_extension(&challenge_base64, ext); ++ if (res < 0) { ++ goto err; ++ } ++ challenge = kpsecl_base64urldecode((const char*)challenge_base64.buf, challenge_base64.filled, &challenge_len); ++ if (challenge == NULL || challenge_len < HASH_OFFSET + HASH_LEN) { ++ goto err; ++ } ++ ra_tls_buf_init(key_hash, HASH_LEN); ++ memcpy(key_hash->buf, &challenge[HASH_OFFSET], HASH_LEN); ++ key_hash->filled = HASH_LEN; ++ ret = 0; ++err: ++ ra_tls_buf_free(&challenge_base64); ++ if (challenge) { ++ free(challenge); ++ } ++ return ret; ++} ++ ++static int check_public_key_hash(ra_tls_buf *key_hash, ra_tls_buf *ker_der) ++{ ++ int ret = -1; ++ ra_tls_buf cal_hash = RA_TLS_BUF_INIT; ++ ra_tls_buf_init(&cal_hash, HASH_LEN); ++ if (key_hash->filled != HASH_LEN) { ++ return -1; ++ } ++ if (0 != get_hash(&cal_hash, ker_der, SHA_256)) { ++ goto err; ++ } ++#ifdef DEBUG ++ printf("compare key hash\n"); ++ printf("expected: "); ++ for (size_t i = 0; i < key_hash->filled; i++) { ++ printf("%02X", key_hash->buf[i]); ++ } ++ printf("\n"); ++ printf("get hash of input: "); ++ for (size_t i = 0; i < cal_hash.filled; i++) { ++ printf("%02X", cal_hash.buf[i]); ++ } ++ printf("\n"); ++#endif ++ if (0 != memcmp(key_hash->buf, cal_hash.buf, cal_hash.filled)) { ++ printf("public key hash check Failed\n"); ++ goto err; ++ } ++ ret = 0; ++err: ++ ra_tls_buf_free(&cal_hash); ++ return ret; ++} ++ ++bool ra_tls_cert_extension_expired(ra_tls_buf *cert) ++{ ++ bool ret = true; ++ ra_tls_buf token = RA_TLS_BUF_INIT; ++ ra_tls_buf oid = RA_TLS_BUF_INIT; ++ ra_tls_buf claim = RA_TLS_BUF_INIT; ++ char *raw_claim = NULL; ++ size_t raw_claim_len = 0; ++ cJSON *root = NULL; ++ cJSON *obj_get = NULL; ++ size_t expired = 0; ++ if (cert == NULL || cert->filled == 0) { ++ goto err; ++ } ++ if (get_extension_from_certificate_der(&token, &oid, cert) < 0) { ++ goto err; ++ } ++ // check extension expired ++ if (parse_claim_from_token(&claim, &token) < 0) { ++ goto err; ++ } ++ raw_claim = (char*)kpsecl_base64urldecode((const char *)claim.buf, claim.filled, &raw_claim_len); ++ if (raw_claim == NULL) { ++ goto err; ++ } ++#ifdef DEBUG ++ printf("raw claim: %s", raw_claim); ++#endif ++ root = cJSON_Parse(raw_claim); ++ if (root == NULL) { ++ goto err; ++ } ++ obj_get = cJSON_GetObjectItemCaseSensitive(root, "exp"); ++ if (obj_get == NULL || !cJSON_IsNumber(obj_get)) { ++ goto err; ++ } ++ expired = cJSON_GetNumberValue(obj_get); ++ if ((size_t)time(NULL) + EXTENSION_EXPIRED_OFFSET_SECONDS >= expired) { ++ ret = true; ++ } else { ++ ret = false; ++ } ++err: ++ ra_tls_buf_free(&token); ++ ra_tls_buf_free(&oid); ++ ra_tls_buf_free(&claim); ++ if (raw_claim) { ++ free(raw_claim); ++ } ++ if (root) { ++ cJSON_Delete(root); ++ } ++ return ret; ++} ++ ++static int verify_certificate_extension(void *cert_ctx) ++{ ++ int res; ++ int ret = -1; ++ ra_tls_buf ext = RA_TLS_BUF_INIT; ++ ra_tls_buf key_hash = RA_TLS_BUF_INIT; ++ ra_tls_buf key_der = RA_TLS_BUF_INIT; ++ ra_tls_buf oid = RA_TLS_BUF_INIT; ++ extension_type type; ++ ++// below depend api declare in ra_tls_imp.h ++ res = get_extension_from_certificate_context(&ext, &oid, cert_ctx); ++ if (res < 0) { ++ printf("get extension from certificate failed\n"); ++ goto err; ++ } ++ if (strcmp((const char*)oid.buf, EVIDENCE_OID) == 0) { ++ type = EVIDENCE; ++ } else if (strcmp((const char*)oid.buf, TOKEN_OID) == 0) { ++ type = TOKEN; ++ } else { ++ goto err; ++ } ++ res = verify_extension(&ext, type); ++ if (res < 0) { ++ printf("extension verfiy failed\n"); ++ goto err; ++ } ++ res = get_public_key_from_certificate_context(&key_der, cert_ctx); ++ if (res < 0) { ++ printf("get public key failed\n"); ++ } ++ res = get_public_key_hash(&key_hash, &ext); ++ if (res < 0) { ++ printf("get public key hash failed\n"); ++ } ++ res = check_public_key_hash(&key_hash, &key_der); ++ if (res < 0) { ++ printf("public key hash check failed\n"); ++ goto err; ++ } ++ // success ++ ret = 0; ++err: ++ ra_tls_buf_free(&ext); ++ ra_tls_buf_free(&key_hash); ++ ra_tls_buf_free(&key_der); ++ ra_tls_buf_free(&oid); ++ return ret; ++} ++ ++#ifdef USE_OPENSSL ++// 0 failed 1 ok ++int ra_tls_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx) ++{ ++ int res; ++ int ret = 1; ++ if (preverify_ok == 0) { ++ res = X509_STORE_CTX_get_error(x509_ctx); ++ if (res == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) { ++ printf("self-signed certificate\n"); ++ ret = 1; ++ return ret; ++ } ++ } ++ res = verify_certificate_extension(x509_ctx); ++ if (res < 0) { ++ ret = 0; ++ } ++ return ret; ++} ++#endif +\ No newline at end of file +diff --git a/component/ra_tls/ra_tls.h b/component/ra_tls/ra_tls.h +new file mode 100644 +index 00000000..a3dca269 +--- /dev/null ++++ b/component/ra_tls/ra_tls.h +@@ -0,0 +1,65 @@ ++/* ++ * 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. ++ */ ++ ++#ifndef RA_TLS_H_ ++#define RA_TLS_H_ ++ ++#ifndef __cplusplus ++#include ++#endif ++#if defined(USE_OPENSSL) ++#include ++#include ++#include ++#elif defined(USE_MBEDTLS) ++#include ++#include ++#include ++#endif ++#include "ra_tls_imp.h" ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++typedef struct { ++ // attestation agent listen address ++ char *aa_addr; ++ char *uuid; ++ ra_mode mode; ++}ra_cfg; ++ ++#define CHALLENGE_LEN 64 ++#define HASH_LEN 32 ++#define HASH_OFFSET 32 ++#define PUBLIC_KEY_HASH_PRINT_LEN (4 * HASH_LEN) ++#define EXTENSION_EXPIRED_OFFSET_SECONDS (5) ++ ++int ra_tls_generate_certificate(ra_tls_buf *cert, ra_tls_buf *private_key, ra_cfg *cfg_ra, key_size size); ++// set attestation agent address, addr is ip:port or domain:port ++int ra_tls_set_addr(char *addr); ++// cert is DER-encoded ++bool ra_tls_cert_extension_expired(ra_tls_buf *cert); ++ ++#if defined(USE_OPENSSL) ++// return 0 failed, 1 ok ++int ra_tls_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx); ++#elif defined(USE_MBEDTLS) ++// return 0 ok, or failed result ++int ra_tls_verify_callback(void *data, mbedtls_509_crt *crt, int depth, uint32_t *flasgs); ++#endif ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif +\ No newline at end of file +diff --git a/component/ra_tls/ra_tls_imp.h b/component/ra_tls/ra_tls_imp.h +new file mode 100644 +index 00000000..5d85ba98 +--- /dev/null ++++ b/component/ra_tls/ra_tls_imp.h +@@ -0,0 +1,97 @@ ++/* ++ * 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. ++ */ ++ ++#ifndef RA_TLS_IMP_H_ ++#define RA_TLS_IMP_H_ ++#include ++#include ++#if defined(USE_OPENSSL) ++#include ++#include ++#include ++#else ++ #error TLS library Must be specified ++#endif ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++// todo register oid ++#define EVIDENCE_OID "1.3.6.1.4.1.2011.2.8" ++#define TOKEN_OID "1.3.6.1.4.1.2011.2.10" ++ ++#define KEY_SIZE_MAX (3072) ++#define DEFAULT_CERT_LIFETIME_YEARS (1) ++ ++typedef struct { ++ uint8_t *buf; ++ size_t len; ++ size_t filled; ++} ra_tls_buf; ++ ++typedef enum key_size_t { ++ RSA_2048, ++ RSA_3072 ++} key_size; ++ ++typedef enum key_type_t { ++ KEY_PUBLIC, ++ KEY_PRIVATE ++} key_type; ++ ++typedef enum hash_type_t { ++ SHA_256, ++ SHA_512 ++} hash_type; ++ ++typedef enum ra_mode_t { ++ BACKGROUND, ++ PASSPORT ++} ra_mode; ++ ++typedef struct { ++ ra_tls_buf prv_key; ++ ra_tls_buf pub_key; ++ char *subject_name; ++ char *issuer_name; ++ char *not_before; // format:YYYYMMDDHHMMSSZ ++ char *not_after; // format:YYYYMMDDHHMMSSZ ++ const char *ext_oid; ++ ra_tls_buf ext; ++} cert_config; ++ ++#define RA_TLS_BUF_INIT {NULL, 0, 0} ++int ra_tls_buf_init(ra_tls_buf *buf, int len); ++void ra_tls_buf_free(ra_tls_buf *buf); ++ ++int get_hash(ra_tls_buf *hash, ra_tls_buf *input, hash_type type); ++int generate_key_pair_der(key_size key_len, ra_tls_buf *public_key, ra_tls_buf *private_key); ++// generate pem certificate,use evidence filled extension specified by oid ++int generate_certificate_with_extension(ra_tls_buf *cert, ra_tls_buf *evidence, ra_tls_buf *public_key, ++ ra_tls_buf *private_key, const char *oid); ++ ++#if defined(USE_OPENSSL) ++/* ++ get extension by oid (EVIDENCE_OID or TOKEN_OID) in certificate ++ cert_ctx associate with library, it may be a runtime context ++*/ ++int get_extension_from_certificate_context(ra_tls_buf *ext_buf, ra_tls_buf *oid, void *cert_ctx); ++int get_public_key_from_certificate_context(ra_tls_buf *key_der, void *cert_ctx); ++int get_extension_from_certificate_der(ra_tls_buf *ext_buf, ra_tls_buf *oid, ra_tls_buf *cert_der); ++#endif ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif +-- +2.46.0 + diff --git a/0091-add-ima-detail-result-in-token.patch b/0091-add-ima-detail-result-in-token.patch deleted file mode 100644 index 217e390fe855025b97986350bff1a6e15eae32cb..0000000000000000000000000000000000000000 --- a/0091-add-ima-detail-result-in-token.patch +++ /dev/null @@ -1,45 +0,0 @@ -From c26a4b5db3eb5ff5d558b9d14f962e3df4147dca Mon Sep 17 00:00:00 2001 -From: houmingyong -Date: Thu, 17 Oct 2024 18:58:00 +0800 -Subject: [PATCH] add ima detail result in token - -Conflict: remove service/attestation/attestation-agent/c_header/example.c ---- - service/attestation/attestation-agent/agent/src/lib.rs | 6 +----- - service/attestation/attestation-service/service/src/lib.rs | 3 +++ - 2 files changed, 4 insertions(+), 5 deletions(-) - -diff --git a/service/attestation/attestation-agent/agent/src/lib.rs b/service/attestation/attestation-agent/agent/src/lib.rs -index 1164e2a..93809a2 100644 ---- a/service/attestation/attestation-agent/agent/src/lib.rs -+++ b/service/attestation/attestation-agent/agent/src/lib.rs -@@ -280,11 +280,7 @@ impl AttestationAgent { - 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) -+ Ok(token_claim as TeeClaim) - }, - Err(e) => { - log::error!("token to teeclaim failed:{:?}", e); -diff --git a/service/attestation/attestation-service/service/src/lib.rs b/service/attestation/attestation-service/service/src/lib.rs -index 1c5c907..dd10b89 100644 ---- a/service/attestation/attestation-service/service/src/lib.rs -+++ b/service/attestation/attestation-service/service/src/lib.rs -@@ -152,6 +152,9 @@ impl AttestationService { - } - } - -+ // 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"))?), --- -2.33.0 - diff --git a/0091-fix-allow-bind-unbind-and-clear-resource-policy-when.patch b/0091-fix-allow-bind-unbind-and-clear-resource-policy-when.patch new file mode 100644 index 0000000000000000000000000000000000000000..4a197f1ca3856f1fdc0b4ad63deea2bf6c1a6d81 --- /dev/null +++ b/0091-fix-allow-bind-unbind-and-clear-resource-policy-when.patch @@ -0,0 +1,119 @@ +From 435f93ddb97be16f60fcd1ace909cafb418f642b Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Wed, 19 Feb 2025 10:59:20 +0800 +Subject: [PATCH 1/1] fix: allow bind, unbind and clear resource policy when + resource already exists + +This influence the interface behavior for: +resource policy bind, +resource policy unbind, +resource policy clear. +--- + .../src/resource/admin/simple.rs | 2 +- + .../src/resource/storage/mod.rs | 9 +++++++-- + .../src/resource/storage/simple.rs | 19 ++++++++++++------- + 3 files changed, 20 insertions(+), 10 deletions(-) + +diff --git a/service/attestation/attestation-types/src/resource/admin/simple.rs b/service/attestation/attestation-types/src/resource/admin/simple.rs +index 5967be9..bf2b5dd 100644 +--- a/service/attestation/attestation-types/src/resource/admin/simple.rs ++++ b/service/attestation/attestation-types/src/resource/admin/simple.rs +@@ -106,7 +106,7 @@ impl ResourceAdminInterface for SimpleResourceAdmin { + self.storage_engine + .lock() + .await +- .store(location, resource) ++ .store(location, resource, false) + .await + } + +diff --git a/service/attestation/attestation-types/src/resource/storage/mod.rs b/service/attestation/attestation-types/src/resource/storage/mod.rs +index fd7b0c7..3614769 100644 +--- a/service/attestation/attestation-types/src/resource/storage/mod.rs ++++ b/service/attestation/attestation-types/src/resource/storage/mod.rs +@@ -29,7 +29,12 @@ pub(crate) trait StorageOp: Send + Sync { + /// 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<()>; ++ async fn store( ++ &self, ++ location: ResourceLocation, ++ resource: Resource, ++ force: bool, ++ ) -> 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. +@@ -51,7 +56,7 @@ pub(crate) trait PolicyOp: StorageOp + Send + Sync { + /// 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<()>; ++ async fn clear_policies(&self, location: ResourceLocation) -> Result<()>; + /// Delete the specific policy from the resource. + async fn unbind_policies( + &self, +diff --git a/service/attestation/attestation-types/src/resource/storage/simple.rs b/service/attestation/attestation-types/src/resource/storage/simple.rs +index b8fd536..d5f1bc5 100644 +--- a/service/attestation/attestation-types/src/resource/storage/simple.rs ++++ b/service/attestation/attestation-types/src/resource/storage/simple.rs +@@ -82,10 +82,15 @@ impl StorageOp for SimpleStorage { + Ok(ret) + } + +- async fn store(&self, location: ResourceLocation, resource: Resource) -> Result<()> { ++ async fn store( ++ &self, ++ location: ResourceLocation, ++ resource: Resource, ++ force: bool, ++ ) -> Result<()> { + let regularized = self.regular(&format!("{}", location))?; + +- if regularized.exists() { ++ if !force && regularized.exists() { + return Err(ResourceError::ResourceExist(location.to_string())); + } + +@@ -132,16 +137,16 @@ impl PolicyOp for SimpleStorage { + ) -> Result<()> { + let mut resource = self.get(location.clone()).await?; + resource.set_policy(policy); +- self.store(location, resource).await ++ self.store(location, resource, true).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<()> { ++ async fn clear_policies(&self, location: ResourceLocation) -> Result<()> { + let mut resource = self.get(location.clone()).await?; + resource.policy = vec![]; +- self.store(location, resource).await ++ self.store(location, resource, true).await + } + async fn unbind_policies( + &self, +@@ -155,7 +160,7 @@ impl PolicyOp for SimpleStorage { + resource.policy.remove(idx); + } + } +- self.store(location, resource).await ++ self.store(location, resource, true).await + } + async fn bind_policies( + &self, +@@ -166,7 +171,7 @@ impl PolicyOp for SimpleStorage { + for p in policy.iter() { + resource.policy.push(format!("{}", p)); + } +- self.store(location.clone(), resource).await ++ self.store(location.clone(), resource, true).await + } + } + +-- +2.46.0 + diff --git a/0092-fix-use-etc-as-the-base-directory-for-resource-stora.patch b/0092-fix-use-etc-as-the-base-directory-for-resource-stora.patch new file mode 100644 index 0000000000000000000000000000000000000000..867f63cf5beae2a14e89fd883d4224f91d01475d --- /dev/null +++ b/0092-fix-use-etc-as-the-base-directory-for-resource-stora.patch @@ -0,0 +1,39 @@ +From 7cd62c2fa0d264ea3a1898d7522cfc55f2b16d39 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Wed, 19 Feb 2025 16:44:44 +0800 +Subject: [PATCH 1/1] fix: use etc as the base directory for resource storage + +--- + .../attestation-types/src/resource/policy/opa/mod.rs | 2 +- + .../attestation-types/src/resource/storage/simple.rs | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/service/attestation/attestation-types/src/resource/policy/opa/mod.rs b/service/attestation/attestation-types/src/resource/policy/opa/mod.rs +index 0ec506a..ad159f5 100644 +--- a/service/attestation/attestation-types/src/resource/policy/opa/mod.rs ++++ b/service/attestation/attestation-types/src/resource/policy/opa/mod.rs +@@ -21,7 +21,7 @@ use async_trait::async_trait; + use std::path::PathBuf; + + pub(crate) const DEFAULT_RESOURCE_POLICY_DIR: &str = +- "/run/attestation/attestation-service/resource/policy/"; ++ "/etc/attestation/attestation-service/resource/policy/"; + pub(crate) const DEFAULT_RESOURCE_VIRTCCA_DEFAULT_POLICY: &str = "virtcca.rego"; + + pub(crate) struct OpenPolicyAgent { +diff --git a/service/attestation/attestation-types/src/resource/storage/simple.rs b/service/attestation/attestation-types/src/resource/storage/simple.rs +index d5f1bc5..dad24e0 100644 +--- a/service/attestation/attestation-types/src/resource/storage/simple.rs ++++ b/service/attestation/attestation-types/src/resource/storage/simple.rs +@@ -24,7 +24,7 @@ use super::PolicyOp; + use super::Resource; + use super::StorageEngine; + +-pub(crate) const STORAGE_BASE: &str = "/run/attestation/attestation-service/resource/storage/"; ++pub(crate) const STORAGE_BASE: &str = "/etc/attestation/attestation-service/resource/storage/"; + + pub(crate) struct SimpleStorage { + base: PathBuf, +-- +2.46.0 + diff --git a/0092-ima-detail-result-exclude-boot_aggregate-file.patch b/0092-ima-detail-result-exclude-boot_aggregate-file.patch deleted file mode 100644 index 5dcfba78604a6a7603455cdc0e9f84163431bcf6..0000000000000000000000000000000000000000 --- a/0092-ima-detail-result-exclude-boot_aggregate-file.patch +++ /dev/null @@ -1,26 +0,0 @@ -From 9908ddc7947c10e0411c0b037160e320d8e83620 Mon Sep 17 00:00:00 2001 -From: houmingyong -Date: Tue, 22 Oct 2024 19:51:26 +0800 -Subject: [PATCH] ima detail result exclude boot_aggregate file - ---- - .../attestation-service/verifier/src/virtcca/ima.rs | 3 +++ - 1 file changed, 3 insertions(+) - -diff --git a/service/attestation/attestation-service/verifier/src/virtcca/ima.rs b/service/attestation/attestation-service/verifier/src/virtcca/ima.rs -index 2b73b46..4a9a954 100644 ---- a/service/attestation/attestation-service/verifier/src/virtcca/ima.rs -+++ b/service/attestation/attestation-service/verifier/src/virtcca/ima.rs -@@ -59,6 +59,9 @@ impl ImaVerify { - EventData::ImaNg{digest, name} => (name, digest.digest), - _ => bail!("Inalid 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)); --- -2.43.0 - diff --git a/0093-add-detailed-log-of-file-opening-failures.patch b/0093-add-detailed-log-of-file-opening-failures.patch deleted file mode 100644 index 16c29294fce474c9ec2b84870942f68983074584..0000000000000000000000000000000000000000 --- a/0093-add-detailed-log-of-file-opening-failures.patch +++ /dev/null @@ -1,74 +0,0 @@ -From 9c04006b8f5281bd5b436f81ec855f78a719dff7 Mon Sep 17 00:00:00 2001 -From: houmingyong -Date: Wed, 18 Dec 2024 09:34:57 +0800 -Subject: [PATCH] add detailed log of file opening failures - ---- - .../attestation-service/verifier/src/virtcca/ima.rs | 8 ++++---- - .../attestation-service/verifier/src/virtcca/mod.rs | 9 ++++++--- - 2 files changed, 10 insertions(+), 7 deletions(-) - -diff --git a/service/attestation/attestation-service/verifier/src/virtcca/ima.rs b/service/attestation/attestation-service/verifier/src/virtcca/ima.rs -index 7af55e8..e25e55e 100644 ---- a/service/attestation/attestation-service/verifier/src/virtcca/ima.rs -+++ b/service/attestation/attestation-service/verifier/src/virtcca/ima.rs -@@ -9,7 +9,7 @@ - * PURPOSE. - * See the Mulan PSL v2 for more details. - */ --use anyhow::{Result, bail}; -+use anyhow::{anyhow, Result, bail}; - use ima_measurements::{Event, EventData, Parser}; - use fallible_iterator::FallibleIterator; - use serde_json::{Value, Map, json}; -@@ -47,7 +47,8 @@ impl ImaVerify { - bail!("ima log hash verify failed"); - } - -- let ima_refs: Vec<_> = file_reader(IMA_REFERENCE_FILE)? -+ let ima_refs: Vec<_> = file_reader(IMA_REFERENCE_FILE) -+ .map_err(|_err| anyhow!("{} is not found", IMA_REFERENCE_FILE))? - .into_iter() - .map(String::from) - .collect(); -@@ -80,8 +81,7 @@ 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) -- .expect("open ima reference file failed"); -+ let file = std::fs::File::open(file_path)?; - let mut strings = Vec::::new(); - let mut reader = BufReader::new(file); - let mut buf = String::new(); -diff --git a/service/attestation/attestation-service/verifier/src/virtcca/mod.rs b/service/attestation/attestation-service/verifier/src/virtcca/mod.rs -index 97f5b6b..42f263a 100644 ---- a/service/attestation/attestation-service/verifier/src/virtcca/mod.rs -+++ b/service/attestation/attestation-service/verifier/src/virtcca/mod.rs -@@ -161,9 +161,11 @@ impl Evidence { - // todo verify cert chain, now only verify signature - fn verify_dev_cert_chain(dev_cert: &[u8]) -> Result<()> { - let dev_cert = x509::X509::from_der(dev_cert)?; -- let sub_cert_file = std::fs::read(VIRTCCA_SUB_CERT)?; -+ let sub_cert_file = std::fs::read(VIRTCCA_SUB_CERT) -+ .map_err(|_err| anyhow!("{} is not found", VIRTCCA_SUB_CERT))?; - let sub_cert = x509::X509::from_pem(&sub_cert_file)?; -- let root_cert_file = std::fs::read(VIRTCCA_ROOT_CERT)?; -+ let root_cert_file = std::fs::read(VIRTCCA_ROOT_CERT) -+ .map_err(|_err| anyhow!("{} is not found", VIRTCCA_ROOT_CERT))?; - let root_cert = x509::X509::from_pem(&root_cert_file)?; - - // verify dev_cert by sub_cert -@@ -229,7 +231,8 @@ impl Evidence { - } - #[cfg(feature = "no_as")] - fn compare_with_ref(&mut self) -> Result<()> { -- let ref_file = std::fs::read(VIRTCCA_REF_VALUE_FILE)?; -+ let ref_file = std::fs::read(VIRTCCA_REF_VALUE_FILE) -+ .map_err(|_err| anyhow!("{} is not found", VIRTCCA_REF_VALUE_FILE))?; - let js_ref = serde_json::from_slice(&ref_file)?; - match js_ref { - serde_json::Value::Object(obj) => { --- -2.43.0 - diff --git a/0093-fix-raise-formatted-error-message-if-http-error-happ.patch b/0093-fix-raise-formatted-error-message-if-http-error-happ.patch new file mode 100644 index 0000000000000000000000000000000000000000..a345f3477160f05a99d20bf5411819ab42d0b63b --- /dev/null +++ b/0093-fix-raise-formatted-error-message-if-http-error-happ.patch @@ -0,0 +1,790 @@ +From a639aa650a7f0045474ec9c01e8dc9747cc3e3fe Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Thu, 27 Feb 2025 20:00:04 +0800 +Subject: [PATCH 1/3] fix: raise formatted error message if http error happens + +--- + .../attestation-client/src/client.rs | 2 +- + .../attestation-client/src/common.rs | 23 ++++ + .../attestation-client/src/error.rs | 22 ++-- + .../attestation-client/src/main.rs | 1 + + .../attestation-client/src/resource/client.rs | 120 ++++-------------- + .../attestation-client/src/resource/mod.rs | 90 ++++++++----- + .../src/resource_policy/client.rs | 107 ++++------------ + .../src/resource_policy/mod.rs | 75 +++++++---- + 8 files changed, 191 insertions(+), 249 deletions(-) + create mode 100644 service/attestation/attestation-client/src/common.rs + +diff --git a/service/attestation/attestation-client/src/client.rs b/service/attestation/attestation-client/src/client.rs +index 2c0f139..1200823 100644 +--- a/service/attestation/attestation-client/src/client.rs ++++ b/service/attestation/attestation-client/src/client.rs +@@ -30,7 +30,7 @@ pub(crate) struct AsClient { + 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()?, ++ Protocal::Http { svr: _ } => Client::builder().cookie_store(cookie_store).build()?, + }; + + Ok(Self { protocal, client }) +diff --git a/service/attestation/attestation-client/src/common.rs b/service/attestation/attestation-client/src/common.rs +new file mode 100644 +index 0000000..5f8ec98 +--- /dev/null ++++ b/service/attestation/attestation-client/src/common.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 reqwest::Response; ++ ++pub(crate) async fn response_display(resp: Response) { ++ if !resp.status().is_success() { ++ println!("{:?}", resp); ++ } ++ let txt = resp.text().await.unwrap(); ++ if !txt.is_empty() { ++ println!("{}", txt); ++ } ++} +diff --git a/service/attestation/attestation-client/src/error.rs b/service/attestation/attestation-client/src/error.rs +index 2952de2..3245efa 100644 +--- a/service/attestation/attestation-client/src/error.rs ++++ b/service/attestation/attestation-client/src/error.rs +@@ -1,14 +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. +- */ ++* 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; +@@ -18,6 +18,4 @@ pub type Result = std::result::Result; + 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 +index a779a71..512055e 100644 +--- a/service/attestation/attestation-client/src/main.rs ++++ b/service/attestation/attestation-client/src/main.rs +@@ -17,6 +17,7 @@ mod client; + mod error; + mod resource; + mod resource_policy; ++mod common; + + use crate::resource::ResourceArgs; + use crate::resource_policy::ResourcePolicyArgs; +diff --git a/service/attestation/attestation-client/src/resource/client.rs b/service/attestation/attestation-client/src/resource/client.rs +index e0dcb08..ecb8a7a 100644 +--- a/service/attestation/attestation-client/src/resource/client.rs ++++ b/service/attestation/attestation-client/src/resource/client.rs +@@ -13,12 +13,12 @@ + //! Implement web request for resource to attestation service + + use crate::client::AsClient; +-use crate::error::{ClientError, Result}; ++use crate::error::Result; + use attestation_types::{ + resource::ResourceLocation, + service::{GetResourceOp, SetResourceOp, SetResourceRequest}, + }; +-use reqwest::Client; ++use reqwest::{Client, Response}; + + pub(crate) struct ResourceClient { + client: AsClient, +@@ -37,27 +37,18 @@ impl ResourceClient { + self.client.client() + } + +- pub(crate) async fn vendor_get_resource(&self, vendor: &str) -> Result> { ++ pub(crate) async fn vendor_get_resource(&self, vendor: &str) -> Result { + let payload = GetResourceOp::VendorGet { + vendor: vendor.to_string(), + }; + +- let res = self ++ Ok(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, +- )) +- } ++ .await?) + } + + pub(crate) async fn vendor_add_resource( +@@ -66,7 +57,7 @@ impl ResourceClient { + path: &str, + content: &str, + policy: &Vec, +- ) -> Result { ++ ) -> Result { + let op = SetResourceOp::Add { + content: content.to_string(), + policy: policy.clone(), +@@ -75,46 +66,32 @@ impl ResourceClient { + op, + resource: ResourceLocation::new(Some(vendor.to_string()), path.to_string()), + }; +- let res = self ++ Ok(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, +- )) +- } ++ .await?) + } + +- pub(crate) async fn vendor_delete_resource(&self, vendor: &str, path: &str) -> Result { ++ 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 ++ Ok(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, +- )) +- } ++ .await?) + } + + pub(crate) async fn vendor_modify_resource( +@@ -122,7 +99,7 @@ impl ResourceClient { + vendor: &str, + path: &str, + content: &str, +- ) -> Result { ++ ) -> Result { + let op = SetResourceOp::Modify { + content: content.to_string(), + }; +@@ -130,22 +107,13 @@ impl ResourceClient { + op, + resource: ResourceLocation::new(Some(vendor.to_string()), path.to_string()), + }; +- let res = self ++ Ok(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, +- )) +- } ++ .await?) + } + + pub(crate) async fn vendor_bind_resource( +@@ -153,7 +121,7 @@ impl ResourceClient { + vendor: &str, + path: &str, + policy: &Vec, +- ) -> Result { ++ ) -> Result { + let op = SetResourceOp::Bind { + policy: policy.clone(), + }; +@@ -161,22 +129,13 @@ impl ResourceClient { + op, + resource: ResourceLocation::new(Some(vendor.to_string()), path.to_string()), + }; +- let res = self ++ Ok(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, +- )) +- } ++ .await?) + } + + pub(crate) async fn vendor_unbind_resource( +@@ -184,7 +143,7 @@ impl ResourceClient { + vendor: &str, + path: &str, + policy: &Vec, +- ) -> Result { ++ ) -> Result { + let op = SetResourceOp::Unbind { + policy: policy.clone(), + }; +@@ -192,43 +151,12 @@ impl ResourceClient { + op, + resource: ResourceLocation::new(Some(vendor.to_string()), path.to_string()), + }; +- let res = self ++ Ok(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, +- )) +- } ++ .await?) + } + } +- +-// 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 +index d198ef4..35c1c5a 100644 +--- a/service/attestation/attestation-client/src/resource/mod.rs ++++ b/service/attestation/attestation-client/src/resource/mod.rs +@@ -17,6 +17,7 @@ pub(crate) mod client; + + use self::client::ResourceClient; + use crate::client::AsClient; ++use crate::common::response_display; + use clap::{Args, Subcommand}; + + #[derive(Debug, Args)] +@@ -61,69 +62,88 @@ pub(crate) enum ResourceCommand { + + impl ResourceArgs { + pub(crate) fn process(&self, base_client: AsClient) { +- self.command.dispatch(base_client); ++ let runtime = tokio::runtime::Runtime::new().unwrap(); ++ runtime.block_on(self.command.dispatch(base_client)); + } + } + + impl ResourceCommand { +- fn dispatch(&self, base_client: AsClient) { ++ async 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::Get { vendor } => match client.vendor_get_resource(vendor).await { ++ Ok(ret) => { ++ response_display(ret).await; ++ } ++ Err(e) => { ++ println!("{:?}", e); ++ } ++ }, + ResourceCommand::Add { + vendor, + path, + content, + policy, + } => { +- let ret = runtime +- .block_on(client.vendor_add_resource(vendor, path, content, policy)) +- .unwrap(); +- println!("{:?}", ret); ++ match client ++ .vendor_add_resource(vendor, path, content, policy) ++ .await ++ { ++ Ok(ret) => { ++ response_display(ret).await; ++ } ++ Err(e) => { ++ println!("{:?}", e); ++ } ++ } + } + ResourceCommand::Delete { vendor, path } => { +- let ret = runtime +- .block_on(client.vendor_delete_resource(vendor, path)) +- .unwrap(); +- println!("{:?}", ret); ++ match client.vendor_delete_resource(vendor, path).await { ++ Ok(ret) => { ++ response_display(ret).await; ++ } ++ Err(e) => { ++ println!("{:?}", e); ++ } ++ } + } + ResourceCommand::Modify { + vendor, + path, + content, +- } => { +- let ret = runtime +- .block_on(client.vendor_modify_resource(vendor, path, content)) +- .unwrap(); +- println!("{:?}", ret); +- } ++ } => match client.vendor_modify_resource(vendor, path, content).await { ++ Ok(ret) => { ++ response_display(ret).await; ++ } ++ Err(rsp) => { ++ println!("{:?}", rsp); ++ } ++ }, + ResourceCommand::BindPolicy { + vendor, + path, + policy, +- } => { +- let ret = runtime +- .block_on(client.vendor_bind_resource(vendor, path, policy)) +- .unwrap(); +- println!("{:?}", ret); +- } ++ } => match client.vendor_bind_resource(vendor, path, policy).await { ++ Ok(ret) => { ++ response_display(ret).await; ++ } ++ Err(rsp) => { ++ println!("{:?}", rsp); ++ } ++ }, + ResourceCommand::UnbindPolicy { + vendor, + path, + policy, +- } => { +- let ret = runtime +- .block_on(client.vendor_unbind_resource(vendor, path, policy)) +- .unwrap(); +- println!("{:?}", ret); +- } ++ } => match client.vendor_unbind_resource(vendor, path, policy).await { ++ Ok(ret) => { ++ response_display(ret).await; ++ } ++ Err(rsp) => { ++ println!("{:?}", rsp); ++ } ++ }, + } + } + } +diff --git a/service/attestation/attestation-client/src/resource_policy/client.rs b/service/attestation/attestation-client/src/resource_policy/client.rs +index 582a6bd..08b75c1 100644 +--- a/service/attestation/attestation-client/src/resource_policy/client.rs ++++ b/service/attestation/attestation-client/src/resource_policy/client.rs +@@ -14,12 +14,12 @@ + //! + + use crate::client::AsClient; +-use crate::error::{ClientError, Result}; ++use crate::error::Result; + use attestation_types::{ + resource::policy::PolicyLocation, + service::{GetResourcePolicyOp, SetResourcePolicyOp}, + }; +-use reqwest::Client; ++use reqwest::{Client, Response}; + + pub(crate) struct ResourcePolicyClient { + client: AsClient, +@@ -38,7 +38,7 @@ impl ResourcePolicyClient { + self.client.client() + } + +- pub(crate) async fn vendor_get_one(&self, vendor: &str, id: &str) -> Result { ++ pub(crate) async fn vendor_get_one(&self, vendor: &str, id: &str) -> Result { + let payload = GetResourcePolicyOp::GetOne { + policy: PolicyLocation { + vendor: Some(vendor.to_string()), +@@ -46,70 +46,44 @@ impl ResourcePolicyClient { + }, + }; + +- let res = self ++ Ok(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, +- )) +- } ++ .await?) + } +- pub(crate) async fn vendor_get_all(&self) -> Result> { ++ pub(crate) async fn vendor_get_all(&self) -> Result { + let payload = GetResourcePolicyOp::GetAll; + +- let res = self ++ Ok(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, +- )) +- } ++ .await?) + } +- pub(crate) async fn vendor_get_all_in_vendor(&self, vendor: &str) -> Result> { ++ pub(crate) async fn vendor_get_all_in_vendor(&self, vendor: &str) -> Result { + let payload = GetResourcePolicyOp::GetAllInVendor { + vendor: vendor.to_string(), + }; + +- let res = self ++ Ok(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, +- )) +- } ++ .await?) + } +- pub(crate) async fn vendor_add(&self, vendor: &str, id: &str, content: &str) -> Result { ++ 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()), +@@ -118,24 +92,15 @@ impl ResourcePolicyClient { + content: content.to_string(), + }; + +- let res = self ++ Ok(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, +- )) +- } ++ .await?) + } +- pub(crate) async fn vendor_delete(&self, vendor: &str, id: &str) -> Result { ++ pub(crate) async fn vendor_delete(&self, vendor: &str, id: &str) -> Result { + let payload = SetResourcePolicyOp::Delete { + policy: PolicyLocation { + vendor: Some(vendor.to_string()), +@@ -143,48 +108,26 @@ impl ResourcePolicyClient { + }, + }; + +- let res = self ++ Ok(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, +- )) +- } ++ .await?) + } + +- pub(crate) async fn vendor_clear_all(&self, vendor: &str) -> Result { ++ pub(crate) async fn vendor_clear_all(&self, vendor: &str) -> Result { + let payload = SetResourcePolicyOp::ClearAll { + vendor: vendor.to_string(), + }; + +- let res = self ++ Ok(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, +- )) +- } ++ .await?) + } + } +diff --git a/service/attestation/attestation-client/src/resource_policy/mod.rs b/service/attestation/attestation-client/src/resource_policy/mod.rs +index 4879412..d8afb6b 100644 +--- a/service/attestation/attestation-client/src/resource_policy/mod.rs ++++ b/service/attestation/attestation-client/src/resource_policy/mod.rs +@@ -17,6 +17,7 @@ pub(crate) mod client; + + use self::client::ResourcePolicyClient; + use crate::client::AsClient; ++use crate::common::response_display; + use clap::{Args, Subcommand}; + + #[derive(Debug, Args)] +@@ -53,47 +54,75 @@ pub(crate) enum ResourcePolicyCommand { + + impl ResourcePolicyArgs { + pub(crate) fn process(&self, base_client: AsClient) { +- self.command.dispatch(base_client); ++ let runtime = tokio::runtime::Runtime::new().unwrap(); ++ runtime.block_on(self.command.dispatch(base_client)); + } + } + + impl ResourcePolicyCommand { +- fn dispatch(&self, base_client: AsClient) { ++ async 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()); ++ match client.vendor_get_one(vendor, id).await { ++ Ok(ret) => { ++ response_display(ret).await; ++ } ++ Err(e) => { ++ println!("{:?}", e); ++ } ++ } + } ++ ResourcePolicyCommand::GetAll => match client.vendor_get_all().await { ++ Ok(ret) => { ++ response_display(ret).await; ++ } ++ Err(rsp) => { ++ println!("{:?}", rsp); ++ } ++ }, + ResourcePolicyCommand::GetAllInVendor { vendor } => { +- let ret = runtime +- .block_on(client.vendor_get_all_in_vendor(vendor)) +- .unwrap(); +- println!("{}", serde_json::json!(ret).to_string()); ++ match client.vendor_get_all_in_vendor(vendor).await { ++ Ok(ret) => { ++ response_display(ret).await; ++ } ++ Err(e) => { ++ println!("{:?}", e); ++ } ++ } + } + ResourcePolicyCommand::Add { + vendor, + id, + content, +- } => { +- let ret = runtime +- .block_on(client.vendor_add(vendor, id, content)) +- .unwrap(); +- println!("{}", ret); +- } ++ } => match client.vendor_add(vendor, id, content).await { ++ Ok(ret) => { ++ response_display(ret).await; ++ } ++ Err(rsp) => { ++ println!("{:?}", rsp); ++ } ++ }, + ResourcePolicyCommand::Delete { vendor, id } => { +- let ret = runtime.block_on(client.vendor_delete(vendor, id)).unwrap(); +- println!("{}", ret); ++ match client.vendor_delete(vendor, id).await { ++ Ok(ret) => { ++ response_display(ret).await; ++ } ++ Err(e) => { ++ println!("{:?}", e); ++ } ++ } + } + ResourcePolicyCommand::ClearAll { vendor } => { +- let ret = runtime.block_on(client.vendor_clear_all(vendor)).unwrap(); +- println!("{}", ret); ++ match client.vendor_clear_all(vendor).await { ++ Ok(ret) => { ++ response_display(ret).await; ++ } ++ Err(e) => { ++ println!("{:?}", e); ++ } ++ } + } + } + } +-- +2.46.0 + diff --git a/0094-fix-use-default-vendor-if-it-is-not-set-or-is-empty.patch b/0094-fix-use-default-vendor-if-it-is-not-set-or-is-empty.patch new file mode 100644 index 0000000000000000000000000000000000000000..0f21a995ea2eec38cdd30ea7640b5c8670fdbfe2 --- /dev/null +++ b/0094-fix-use-default-vendor-if-it-is-not-set-or-is-empty.patch @@ -0,0 +1,42 @@ +From 28d4f96fa92f342a32b4f7e145db964291a111a4 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Mon, 3 Mar 2025 21:23:43 +0800 +Subject: [PATCH 2/3] fix: use default vendor if it is not set or is empty + +--- + .../attestation-types/src/resource/mod.rs | 19 +++++++++++-------- + 1 file changed, 11 insertions(+), 8 deletions(-) + +diff --git a/service/attestation/attestation-types/src/resource/mod.rs b/service/attestation/attestation-types/src/resource/mod.rs +index 037c086..f5c7fa8 100644 +--- a/service/attestation/attestation-types/src/resource/mod.rs ++++ b/service/attestation/attestation-types/src/resource/mod.rs +@@ -50,14 +50,17 @@ impl std::convert::TryFrom for PathBuf { + + 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, +- ) ++ let vendor = self ++ .vendor ++ .clone() ++ .unwrap_or(DEFAULT_VENDOR_BASE.to_string()); ++ let v = if vendor.is_empty() { ++ DEFAULT_VENDOR_BASE.to_string() ++ } else { ++ vendor ++ }; ++ ++ write!(f, "{}/{}", v, self.path,) + } + } + +-- +2.46.0 + diff --git a/0095-fix-abort-binding-policies-if-any-policy-location-is.patch b/0095-fix-abort-binding-policies-if-any-policy-location-is.patch new file mode 100644 index 0000000000000000000000000000000000000000..5494eda97f211228aecae52716e01a59b9831ea8 --- /dev/null +++ b/0095-fix-abort-binding-policies-if-any-policy-location-is.patch @@ -0,0 +1,40 @@ +From 6aa683bcbb5a04a79d2784bac2edc3cc1ad0e1b5 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Mon, 3 Mar 2025 21:46:14 +0800 +Subject: [PATCH 3/3] fix: abort binding policies if any policy location is + illegal + +--- + .../src/resource/admin/simple.rs | 15 +++++++-------- + 1 file changed, 7 insertions(+), 8 deletions(-) + +diff --git a/service/attestation/attestation-types/src/resource/admin/simple.rs b/service/attestation/attestation-types/src/resource/admin/simple.rs +index bf2b5dd..641d903 100644 +--- a/service/attestation/attestation-types/src/resource/admin/simple.rs ++++ b/service/attestation/attestation-types/src/resource/admin/simple.rs +@@ -118,15 +118,14 @@ impl ResourceAdminInterface for SimpleResourceAdmin { + 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); ++ 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() +-- +2.46.0 + diff --git a/0096-fix-use-default-vendor-for-resource-policy-if-it-is-.patch b/0096-fix-use-default-vendor-for-resource-policy-if-it-is-.patch new file mode 100644 index 0000000000000000000000000000000000000000..b5a4dab76d5c590cb3a598a81cd0d8fb59232920 --- /dev/null +++ b/0096-fix-use-default-vendor-for-resource-policy-if-it-is-.patch @@ -0,0 +1,44 @@ +From 59f5903023a97bacae25abf2e097156bbd9a3225 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Mon, 3 Mar 2025 21:23:43 +0800 +Subject: [PATCH 1/3] fix: use default vendor for resource policy if it is not + set or is empty + +--- + .../src/resource/policy/mod.rs | 20 +++++++++++-------- + 1 file changed, 12 insertions(+), 8 deletions(-) + +diff --git a/service/attestation/attestation-types/src/resource/policy/mod.rs b/service/attestation/attestation-types/src/resource/policy/mod.rs +index d7ae01d..6ad05dd 100644 +--- a/service/attestation/attestation-types/src/resource/policy/mod.rs ++++ b/service/attestation/attestation-types/src/resource/policy/mod.rs +@@ -64,14 +64,18 @@ impl std::convert::TryFrom<&PolicyLocation> for PathBuf { + + 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, +- ) ++ let vendor = self ++ .vendor ++ .clone() ++ .unwrap_or(DEFAULT_VENDOR_BASE.to_string()); ++ ++ let v = if vendor.is_empty() { ++ DEFAULT_VENDOR_BASE.to_string() ++ } else { ++ vendor ++ }; ++ ++ write!(f, "{}/{}", v, self.id,) + } + } + +-- +2.46.0 + diff --git a/0097-fix-fix-incorrect-error-message-when-modifying-a-non.patch b/0097-fix-fix-incorrect-error-message-when-modifying-a-non.patch new file mode 100644 index 0000000000000000000000000000000000000000..29a2b478610bc5a238cf5126934d9b66f20251cd --- /dev/null +++ b/0097-fix-fix-incorrect-error-message-when-modifying-a-non.patch @@ -0,0 +1,81 @@ +From 5d920a9902e66565c92e7a62b025fdceb5a1647d Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Tue, 4 Mar 2025 10:36:29 +0800 +Subject: [PATCH 2/3] fix: fix incorrect error message when modifying a + non-existing resource + +--- + .../service/src/restapi/resource/storage.rs | 4 +--- + service/attestation/attestation-types/src/resource/error.rs | 6 +++--- + service/attestation/attestation-types/src/resource/mod.rs | 2 +- + .../attestation-types/src/resource/storage/simple.rs | 2 +- + 4 files changed, 6 insertions(+), 8 deletions(-) + +diff --git a/service/attestation/attestation-service/service/src/restapi/resource/storage.rs b/service/attestation/attestation-service/service/src/restapi/resource/storage.rs +index 7b90cda..18fcfab 100644 +--- a/service/attestation/attestation-service/service/src/restapi/resource/storage.rs ++++ b/service/attestation/attestation-service/service/src/restapi/resource/storage.rs +@@ -108,9 +108,7 @@ async fn tee_get_resource( + } + Err(e) => { + log::debug!("{}", e); +- Err(result::AsError::Resource( +- attestation_types::resource::error::ResourceError::LoadPolicy(e), +- )) ++ Err(result::AsError::Other(e)) + } + } + } +diff --git a/service/attestation/attestation-types/src/resource/error.rs b/service/attestation/attestation-types/src/resource/error.rs +index 296aae8..8061730 100644 +--- a/service/attestation/attestation-types/src/resource/error.rs ++++ b/service/attestation/attestation-types/src/resource/error.rs +@@ -20,8 +20,8 @@ pub enum ResourceError { + NotImplemented, + #[error("Policy is missing.")] + PolicyMissing, +- #[error("Failed to load policy: {0}")] +- LoadPolicy(#[from] anyhow::Error), ++ #[error("{0}")] ++ Other(#[from] anyhow::Error), + #[error("Resource error: {0}")] + ResourceError(#[from] std::io::Error), + #[error("Illegal resource path: {0}")] +@@ -35,7 +35,7 @@ pub enum ResourceError { + #[error("Unmatched vendor between resource {0} and policy {1}")] + UnmatchedPolicyResource(String, String), + #[error("Convert error: {0}")] +- IoError(#[from] core::convert::Infallible), ++ ConvertError(#[from] core::convert::Infallible), + #[error("Strip Prefix fail: {0}")] + StripPrefix(#[from] StripPrefixError), + #[error("Illegal policy suffix: {0}")] +diff --git a/service/attestation/attestation-types/src/resource/mod.rs b/service/attestation/attestation-types/src/resource/mod.rs +index f5c7fa8..66007d6 100644 +--- a/service/attestation/attestation-types/src/resource/mod.rs ++++ b/service/attestation/attestation-types/src/resource/mod.rs +@@ -136,7 +136,7 @@ impl Resource { + pub(crate) async fn read_from_file(path: PathBuf) -> Result { + let content = tokio::fs::read(path) + .await +- .context("failed to add resource")?; ++ .context("failed to read resource")?; + Ok(serde_json::from_str( + &String::from_utf8(content).context("from utf8 error")?, + )?) +diff --git a/service/attestation/attestation-types/src/resource/storage/simple.rs b/service/attestation/attestation-types/src/resource/storage/simple.rs +index dad24e0..7ac9fdf 100644 +--- a/service/attestation/attestation-types/src/resource/storage/simple.rs ++++ b/service/attestation/attestation-types/src/resource/storage/simple.rs +@@ -105,7 +105,7 @@ impl StorageOp for SimpleStorage { + } + tokio::fs::write(regularized, serde_json::to_string(&resource)?) + .await +- .context("failed to add resource")?; ++ .context("failed to store resource")?; + Ok(()) + } + +-- +2.46.0 + diff --git a/0098-fix-avoid-clear-resource-policies-if-illegal-vendor-.patch b/0098-fix-avoid-clear-resource-policies-if-illegal-vendor-.patch new file mode 100644 index 0000000000000000000000000000000000000000..18255f93bcc22d9ad0ad2430511061ff8faa53ea --- /dev/null +++ b/0098-fix-avoid-clear-resource-policies-if-illegal-vendor-.patch @@ -0,0 +1,106 @@ +From dbe45fa419ff70f3f0077efd6359a1cc253f2bfc Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Tue, 4 Mar 2025 15:27:24 +0800 +Subject: [PATCH 3/3] fix: avoid clear resource policies if illegal vendor is + given + +--- + .../attestation-types/src/resource/error.rs | 2 ++ + .../src/resource/policy/opa/mod.rs | 33 +++++++++++++++---- + 2 files changed, 29 insertions(+), 6 deletions(-) + +diff --git a/service/attestation/attestation-types/src/resource/error.rs b/service/attestation/attestation-types/src/resource/error.rs +index 8061730..1ce2cbe 100644 +--- a/service/attestation/attestation-types/src/resource/error.rs ++++ b/service/attestation/attestation-types/src/resource/error.rs +@@ -26,6 +26,8 @@ pub enum ResourceError { + ResourceError(#[from] std::io::Error), + #[error("Illegal resource path: {0}")] + IllegalResource(String), ++ #[error("Illegal vendor: {0}")] ++ IllegalVendor(String), + #[error("Invalid resource content: {0}")] + ResourceFromUtf8(#[from] std::string::FromUtf8Error), + #[error("Serde deserialize failure: {0}")] +diff --git a/service/attestation/attestation-types/src/resource/policy/opa/mod.rs b/service/attestation/attestation-types/src/resource/policy/opa/mod.rs +index ad159f5..8e2486a 100644 +--- a/service/attestation/attestation-types/src/resource/policy/opa/mod.rs ++++ b/service/attestation/attestation-types/src/resource/policy/opa/mod.rs +@@ -33,6 +33,27 @@ impl OpenPolicyAgent { + OpenPolicyAgent { base } + } + ++ pub(crate) fn regular(&self, vendor: &str) -> Result { ++ if !Self::check_vendor_legal(vendor) { ++ return Err(ResourceError::IllegalVendor(vendor.to_string())); ++ } ++ ++ let v = if vendor.is_empty() { ++ DEFAULT_VENDOR_BASE ++ } else { ++ vendor ++ }; ++ ++ Ok(self.base.join(v)) ++ } ++ ++ pub(crate) fn check_vendor_legal(vendor: &str) -> bool { ++ if vendor.contains('.') { ++ return false; ++ } ++ true ++ } ++ + pub fn default() -> Self { + Self::new(PathBuf::from(DEFAULT_RESOURCE_POLICY_DIR)) + } +@@ -136,13 +157,13 @@ impl PolicyEngine for OpenPolicyAgent { + } + + async fn get_policy(&self, path: PolicyLocation) -> Result { +- let p = self.base.join(format!("{}", path)); ++ let p = self.regular(&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)); ++ let p = self.regular(&format!("{}", path))?; + if let Some(parent) = p.parent() { + if let Err(e) = tokio::fs::create_dir_all(parent).await { + log::warn!( +@@ -157,7 +178,7 @@ impl PolicyEngine for OpenPolicyAgent { + } + + async fn delete_policy(&self, path: PolicyLocation) -> Result<()> { +- let p = self.base.join(format!("{}", path)); ++ let p = self.regular(&format!("{}", path))?; + tokio::fs::remove_file(p).await?; + Ok(()) + } +@@ -200,7 +221,7 @@ impl PolicyEngine for OpenPolicyAgent { + } + + async fn get_all_policy_in_vendor(&self, vendor: &str) -> Result> { +- let vendor_dir = self.base.join(&vendor); ++ let vendor_dir = self.regular(vendor)?; + let mut dir = tokio::fs::read_dir(vendor_dir).await?; + let mut ret: Vec = vec![]; + while let Some(d) = dir.next_entry().await? { +@@ -264,10 +285,10 @@ impl PolicyEngine for OpenPolicyAgent { + } + + async fn clear_all_policy_in_vendor(&self, vendor: &str) -> Result<()> { +- let vendor_dir = self.base.join(&vendor); ++ let vendor_dir = self.regular(vendor)?; + let md = tokio::fs::metadata(&vendor_dir) + .await +- .context("invalid vendor")?; ++ .context("fetching metadata failed")?; + if md.is_dir() { + tokio::fs::remove_dir_all(vendor_dir).await?; + } +-- +2.46.0 + diff --git a/0099-fix-permit-dots-in-the-resource-policy-id.patch b/0099-fix-permit-dots-in-the-resource-policy-id.patch new file mode 100644 index 0000000000000000000000000000000000000000..621a672f48ed729cdeea5a218d31ba353ed4e8db --- /dev/null +++ b/0099-fix-permit-dots-in-the-resource-policy-id.patch @@ -0,0 +1,125 @@ +From ff132ef73f293a5627a4dae58417a2c571fb6674 Mon Sep 17 00:00:00 2001 +From: chenjiayi +Date: Mon, 10 Mar 2025 14:34:55 +0800 +Subject: [PATCH 1/1] fix: permit dots in the resource policy id + +The resource policy id ends with '.rego', thus dots should be allowed +when checking the legitimacy of resource policy id. +--- + .../src/resource/policy/mod.rs | 16 ++++++++++++ + .../src/resource/policy/opa/mod.rs | 25 +++++++++++++------ + 2 files changed, 33 insertions(+), 8 deletions(-) + +diff --git a/service/attestation/attestation-types/src/resource/policy/mod.rs b/service/attestation/attestation-types/src/resource/policy/mod.rs +index 6ad05dd..46f46ae 100644 +--- a/service/attestation/attestation-types/src/resource/policy/mod.rs ++++ b/service/attestation/attestation-types/src/resource/policy/mod.rs +@@ -33,6 +33,22 @@ pub struct PolicyLocation { + pub id: String, + } + ++impl PolicyLocation { ++ pub(crate) fn check_legal(&self) -> bool { ++ if let Some(v) = &self.vendor { ++ if v.contains(['.', '/']) { ++ return false; ++ } ++ } ++ ++ if self.id.contains(['/']) || !self.id.ends_with(".rego") { ++ return false; ++ } ++ ++ true ++ } ++} ++ + impl std::convert::From for String { + fn from(value: PolicyLocation) -> Self { + format!("{}", value) +diff --git a/service/attestation/attestation-types/src/resource/policy/opa/mod.rs b/service/attestation/attestation-types/src/resource/policy/opa/mod.rs +index 8e2486a..d702061 100644 +--- a/service/attestation/attestation-types/src/resource/policy/opa/mod.rs ++++ b/service/attestation/attestation-types/src/resource/policy/opa/mod.rs +@@ -16,7 +16,7 @@ use crate::resource::{ + policy::PolicyEngine, + ResourceLocation, DEFAULT_VENDOR_BASE, + }; +-use anyhow::{bail, Context}; ++use anyhow::Context; + use async_trait::async_trait; + use std::path::PathBuf; + +@@ -33,7 +33,16 @@ impl OpenPolicyAgent { + OpenPolicyAgent { base } + } + +- pub(crate) fn regular(&self, vendor: &str) -> Result { ++ pub(crate) fn regular_policy(&self, policy: &PolicyLocation) -> Result { ++ let p = policy.to_string(); ++ if !policy.check_legal() { ++ return Err(ResourceError::IllegalPolicyLocation(p)); ++ } ++ ++ Ok(self.base.join(p)) ++ } ++ ++ pub(crate) fn regular_vendor(&self, vendor: &str) -> Result { + if !Self::check_vendor_legal(vendor) { + return Err(ResourceError::IllegalVendor(vendor.to_string())); + } +@@ -48,7 +57,7 @@ impl OpenPolicyAgent { + } + + pub(crate) fn check_vendor_legal(vendor: &str) -> bool { +- if vendor.contains('.') { ++ if vendor.contains(['.', '/']) { + return false; + } + true +@@ -157,13 +166,13 @@ impl PolicyEngine for OpenPolicyAgent { + } + + async fn get_policy(&self, path: PolicyLocation) -> Result { +- let p = self.regular(&format!("{}", path))?; ++ let p = self.regular_policy(&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.regular(&format!("{}", path))?; ++ let p = self.regular_policy(&path)?; + if let Some(parent) = p.parent() { + if let Err(e) = tokio::fs::create_dir_all(parent).await { + log::warn!( +@@ -178,7 +187,7 @@ impl PolicyEngine for OpenPolicyAgent { + } + + async fn delete_policy(&self, path: PolicyLocation) -> Result<()> { +- let p = self.regular(&format!("{}", path))?; ++ let p = self.regular_policy(&path)?; + tokio::fs::remove_file(p).await?; + Ok(()) + } +@@ -221,7 +230,7 @@ impl PolicyEngine for OpenPolicyAgent { + } + + async fn get_all_policy_in_vendor(&self, vendor: &str) -> Result> { +- let vendor_dir = self.regular(vendor)?; ++ let vendor_dir = self.regular_vendor(vendor)?; + let mut dir = tokio::fs::read_dir(vendor_dir).await?; + let mut ret: Vec = vec![]; + while let Some(d) = dir.next_entry().await? { +@@ -285,7 +294,7 @@ impl PolicyEngine for OpenPolicyAgent { + } + + async fn clear_all_policy_in_vendor(&self, vendor: &str) -> Result<()> { +- let vendor_dir = self.regular(vendor)?; ++ let vendor_dir = self.regular_vendor(vendor)?; + let md = tokio::fs::metadata(&vendor_dir) + .await + .context("fetching metadata failed")?; +-- +2.46.0 + diff --git a/0100-challenge-may-generate-by-requester-so-aa-and-as-may.patch b/0100-challenge-may-generate-by-requester-so-aa-and-as-may.patch new file mode 100644 index 0000000000000000000000000000000000000000..e750c0b25534066ceeb2f28c17c0846fcaf19cdf --- /dev/null +++ b/0100-challenge-may-generate-by-requester-so-aa-and-as-may.patch @@ -0,0 +1,155 @@ +From 54e0d971449ac9b8f30bad4f15adb82d42395dc6 Mon Sep 17 00:00:00 2001 +From: xuraoqing +Date: Tue, 11 Mar 2025 08:22:18 +0800 +Subject: [PATCH 1/3] challenge may generate by requester,so aa and as may can + not get previous session by challenge + +Signed-off-by: xuraoqing +--- + .../attestation-agent/agent/src/lib.rs | 37 ++++++------ + .../service/src/restapi/mod.rs | 57 +++++++++++-------- + 2 files changed, 51 insertions(+), 43 deletions(-) + +diff --git a/service/attestation/attestation-agent/agent/src/lib.rs b/service/attestation/attestation-agent/agent/src/lib.rs +index d1234d1..dae5559 100644 +--- a/service/attestation/attestation-agent/agent/src/lib.rs ++++ b/service/attestation/attestation-agent/agent/src/lib.rs +@@ -320,23 +320,11 @@ impl AttestationAgent { + policy_id: Option>, + ) -> Result { + let challenge = String::from_utf8_lossy(challenge).to_string(); +- let mut session = match self ++ let ss = 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()); +- } ++ .await; + + let request_body = json!({ + "challenge": challenge, +@@ -344,9 +332,20 @@ impl AttestationAgent { + "policy_id": policy_id, + }); + let mut map = HeaderMap::new(); +- map.insert("Content-Type", HeaderValue::from_static("application/json")); +- map.insert("as-challenge", HeaderValue::from_static("as")); +- let client = session.get().as_client.clone(); ++ let client; ++ if ss.is_none() { ++ client = self.create_client(self.config.protocal.clone(), true)?; ++ map.insert("Content-Type", HeaderValue::from_static("application/json")); ++ } else { ++ // If the session is already attested, directly use the token. ++ if let Some(t) = ss.as_ref().unwrap().get().token.as_ref() { ++ return Ok(t.clone()); ++ } ++ map.insert("Content-Type", HeaderValue::from_static("application/json")); ++ map.insert("as-challenge", HeaderValue::from_static("as")); ++ client = ss.as_ref().unwrap().get().as_client.clone(); ++ } ++ + let attest_endpoint = format!("{}/attestation", self.config.svr_url); + let res = client + .post(attest_endpoint) +@@ -358,7 +357,9 @@ impl AttestationAgent { + match res.status() { + reqwest::StatusCode::OK => { + let token = res.text().await?; +- session.get_mut().token = Some(token.clone()); ++ if ss.as_ref().is_some() { ++ ss.unwrap().get_mut().token = Some(token.clone()); ++ } + log::debug!("Remote Attestation success, AS Response: {:?}", token); + Ok(token) + } +diff --git a/service/attestation/attestation-service/service/src/restapi/mod.rs b/service/attestation/attestation-service/service/src/restapi/mod.rs +index c3d6309..26a0f84 100644 +--- a/service/attestation/attestation-service/service/src/restapi/mod.rs ++++ b/service/attestation/attestation-service/service/src/restapi/mod.rs +@@ -77,30 +77,32 @@ pub async fn attestation( + let map = service.read().await.get_sessions(); + let request = request.0; + let challenge = request.challenge; +- +- if http_req.headers().contains_key("as-challenge") { +- 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 mut cookie_exist = false; ++ let mut cookie = actix_web::cookie::Cookie::new("init", "init"); ++ if !http_req.headers().contains_key("as-challenge") { ++ log::info!("attestation request lacks 'as-challenge' header field."); ++ } else { ++ log::info!("sessions map len:{}", map.session_map.len()); ++ cookie = http_req ++ .cookie("oeas-session-id") ++ .ok_or(AsError::CookieMissing)?; ++ cookie_exist = true; ++ 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); ++ } + } + + // The challenge in evidence is base64 encoded. +@@ -113,7 +115,12 @@ pub async fn attestation( + .evaluate(&nonce, &evidence, &ids) + .await?; + +- Ok(HttpResponse::Ok().cookie(cookie).body(token)) ++ if cookie_exist { ++ Ok(HttpResponse::Ok().cookie(cookie).body(token)) ++ } else { ++ Ok(HttpResponse::Ok().body(token)) ++ } ++ + } + + #[derive(Deserialize, Serialize, Debug)] +-- +2.33.0 + diff --git a/0101-generate-random-by-ra_tls-itself.patch b/0101-generate-random-by-ra_tls-itself.patch new file mode 100644 index 0000000000000000000000000000000000000000..8dccb6f243b89bd195ccdd299453ddcebb12290c --- /dev/null +++ b/0101-generate-random-by-ra_tls-itself.patch @@ -0,0 +1,176 @@ +From 7ca3d2ff7269501d4810c10e209e18a70fdfda95 Mon Sep 17 00:00:00 2001 +From: xuraoqing +Date: Tue, 11 Mar 2025 12:41:24 +0800 +Subject: [PATCH 2/3] generate random by ra_tls itself + +Signed-off-by: xuraoqing +--- + component/ra_tls/openssl_imp.c | 14 ++++++ + component/ra_tls/ra_tls.c | 87 +++++++++------------------------- + component/ra_tls/ra_tls_imp.h | 1 + + 3 files changed, 37 insertions(+), 65 deletions(-) + +diff --git a/component/ra_tls/openssl_imp.c b/component/ra_tls/openssl_imp.c +index 82e0fd0..19bb280 100644 +--- a/component/ra_tls/openssl_imp.c ++++ b/component/ra_tls/openssl_imp.c +@@ -16,6 +16,7 @@ + #include + #include + #include ++#include + #include "ra_tls_imp.h" + + #define OID_LEN_MAX 64 +@@ -539,6 +540,19 @@ int get_hash(ra_tls_buf *hash, ra_tls_buf *input, hash_type type) + return 0; + } + ++int get_random(uint8_t *random, size_t len) ++{ ++ if (random == NULL) { ++ printf("buf for random is empty\n"); ++ return -1; ++ } ++ if (RAND_bytes(random, len) != 1) { ++ printf("generate random failed\n"); ++ return -1; ++ } ++ return 0; ++} ++ + int generate_certificate_with_extension(ra_tls_buf *cert, ra_tls_buf *extension, ra_tls_buf *public_key, + ra_tls_buf *private_key, const char *oid) + { +diff --git a/component/ra_tls/ra_tls.c b/component/ra_tls/ra_tls.c +index eba084f..b0ba9a0 100644 +--- a/component/ra_tls/ra_tls.c ++++ b/component/ra_tls/ra_tls.c +@@ -220,64 +220,15 @@ static int get_token(ra_tls_buf *token, const char *endpoint_prefix, const char + return ret; + } + +-static char *fill_endpoint(char **endpoint, const char *prefix, const char *source) +-{ +- size_t endpoint_len = 0; +- endpoint_len = strlen(prefix) + strlen(source) + 1; +- *endpoint = malloc(endpoint_len); +- if (*endpoint == NULL) { +- return NULL; +- } +- strcpy(*endpoint, prefix); +- strcat(*endpoint, source); +- return *endpoint; +-} +- +-static char *fill_http_data(char **data, ra_tls_buf *key_hash) +-{ +- const char *http_data_format = "{\"user_data\":[]}"; +- size_t data_len = 0; +- int res; +- char *ptr; +- data_len = strlen(http_data_format) + PUBLIC_KEY_HASH_PRINT_LEN + strlen("]}") + 1; +- *data = malloc(data_len); +- if (*data == NULL) { +- return NULL; +- } +- ptr = *data; +- res = sprintf(ptr, "{\"user_data\":["); +- ptr += res; +- for (size_t i = 0; i < key_hash->filled; i++) { +- res = sprintf(ptr, "%hhu,", key_hash->buf[i]); +- ptr += res; +- if (ptr >= *data + data_len) { +- goto err; +- } +- } +- // point to last character +- --ptr; +- if (data_len - (ptr - *data) < 3) { // 3 means min buffer left to filled +- goto err; +- } +- (void)sprintf(ptr, "]}"); +- goto end; +-err: +- if (*data) { +- free(*data); +- *data = NULL; +- } +-end: +- return *data; +-} +- + static int get_challenge(ra_tls_buf *challenge, const char *endpoint_prefix, ra_tls_buf *user_data) + { + int res; + int ret = -1; +- const char *source_name = "challenge"; +- char *endpoint = NULL; +- char *http_data = NULL; ++ const size_t challenge_len = 32; // 32 means the length of challenge by default ++ size_t base64_len = 0; ++ uint8_t *base64 = NULL; + ra_tls_buf key_hash = RA_TLS_BUF_INIT; ++ ra_tls_buf challenge_raw = RA_TLS_BUF_INIT; + ra_tls_buf *pub_key = user_data; + if (endpoint_prefix == NULL || challenge == NULL || pub_key == NULL) { + return -1; +@@ -291,25 +242,31 @@ static int get_challenge(ra_tls_buf *challenge, const char *endpoint_prefix, ra_ + } + printf("\n"); + #endif +- if (fill_endpoint(&endpoint, endpoint_prefix, source_name) == NULL) { +- goto err; +- } +- if (fill_http_data(&http_data, &key_hash) == NULL) { ++// generate random 32B, concate with public key hash, then base64_url_encode ++ ra_tls_buf_init(&challenge_raw, challenge_len + key_hash.filled); ++ res = get_random(challenge_raw.buf, challenge_len); ++ if (res < 0) { ++ printf("get random failed\n"); + goto err; + } +- res = http_request(endpoint, "GET", http_data, challenge); +- if (res < 0) { ++ memcpy(challenge_raw.buf + challenge_len, key_hash.buf, key_hash.filled); ++ challenge_raw.filled = challenge_len + key_hash.filled; ++ ++ base64 = (uint8_t*)kpsecl_base64urlencode(challenge_raw.buf, challenge_raw.filled, &base64_len); ++ if (base64 == NULL) { + goto err; + } ++ ++ ra_tls_buf_init(challenge, base64_len); ++ memcpy(challenge->buf, base64, base64_len); ++ challenge->filled = base64_len; + ret = 0; + err: +- if (endpoint) { +- free(endpoint); +- } +- if (http_data) { +- free(http_data); +- } + ra_tls_buf_free(&key_hash); ++ ra_tls_buf_free(&challenge_raw); ++ if (base64) { ++ free(base64); ++ } + return ret; + } + +diff --git a/component/ra_tls/ra_tls_imp.h b/component/ra_tls/ra_tls_imp.h +index 5d85ba9..7440a8d 100644 +--- a/component/ra_tls/ra_tls_imp.h ++++ b/component/ra_tls/ra_tls_imp.h +@@ -79,6 +79,7 @@ int generate_key_pair_der(key_size key_len, ra_tls_buf *public_key, ra_tls_buf * + // generate pem certificate,use evidence filled extension specified by oid + int generate_certificate_with_extension(ra_tls_buf *cert, ra_tls_buf *evidence, ra_tls_buf *public_key, + ra_tls_buf *private_key, const char *oid); ++int get_random(uint8_t *random, size_t len); + + #if defined(USE_OPENSSL) + /* +-- +2.33.0 + diff --git a/secGear.spec b/secGear.spec index 00364095e434bd9bed7737ed8f421b39df2b96a3..5034f1fbe668f37064ce45bc54f5dd622f52925c 100644 --- a/secGear.spec +++ b/secGear.spec @@ -1,6 +1,6 @@ Name: secGear Version: 0.1.0 -Release: 54 +Release: 55 Summary: secGear is an SDK to develop confidential computing apps based on hardware enclave features @@ -98,11 +98,19 @@ 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-add-parse-report-c-interface.patch -Patch89: 0090-add-no_as-ima-reference-path.patch -Patch90: 0091-add-ima-detail-result-in-token.patch -Patch91: 0092-ima-detail-result-exclude-boot_aggregate-file.patch -Patch92: 0093-add-detailed-log-of-file-opening-failures.patch +Patch88: 0089-features-support-resource-maitainance.patch +Patch89: 0090-add-ra_tls-support.patch +Patch90: 0091-fix-allow-bind-unbind-and-clear-resource-policy-when.patch +Patch91: 0092-fix-use-etc-as-the-base-directory-for-resource-stora.patch +Patch92: 0093-fix-raise-formatted-error-message-if-http-error-happ.patch +Patch93: 0094-fix-use-default-vendor-if-it-is-not-set-or-is-empty.patch +Patch94: 0095-fix-abort-binding-policies-if-any-policy-location-is.patch +Patch95: 0096-fix-use-default-vendor-for-resource-policy-if-it-is-.patch +Patch96: 0097-fix-fix-incorrect-error-message-when-modifying-a-non.patch +Patch97: 0098-fix-avoid-clear-resource-policies-if-illegal-vendor-.patch +Patch98: 0099-fix-permit-dots-in-the-resource-policy-id.patch +Patch99: 0100-challenge-may-generate-by-requester-so-aa-and-as-may.patch +Patch100: 0101-generate-random-by-ra_tls-itself.patch BuildRequires: gcc python automake autoconf libtool BUildRequires: glibc glibc-devel cmake ocaml-dune rpm gcc-c++ compat-openssl11-libs compat-openssl11-devel @@ -154,13 +162,32 @@ 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 +%package ra_tls +Summary: RA_TLS for %{name} +Requires: cjson curl compat-openssl11-libs +BUildRequires: libcurl-devel cjson-devel compat-openssl11-devel +%description ra_tls +The %{name}-ra_tls package support tls depend on remote attestation + +%package ra_tls-devel +Summary: Development files for %{name}-ra_tls-devel +Requires: libcurl-devel cjson-devel compat-openssl11-devel +%description ra_tls-devel +The %{name}-ra_tls-devel contains Header file for using RA_TLS + %prep %autosetup -n %{name} -p1 %ifnarch x86_64 cd %{_builddir}/%{name}/service/attestation/attestation-agent/ tar xf %{SOURCE1} + mkdir -p .cargo touch .cargo/config.toml cat << EOF >> ./.cargo/config.toml @@ -175,6 +202,16 @@ cd %{_builddir}/%{name}/service/attestation/attestation-service/ tar xf %{SOURCE1} mkdir -p .cargo cp %{_builddir}/%{name}/service/attestation/attestation-agent/.cargo/config.toml .cargo/ + +cd %{_builddir}/%{name}/service/attestation/attestation-client/ +tar xf %{SOURCE1} +mkdir -p .cargo/ +cp %{_builddir}/%{name}/service/attestation/attestation-agent/.cargo/config.toml .cargo/ + +cd %{_builddir}/%{name}/service/attestation/attestation-types/ +tar xf %{SOURCE1} +mkdir -p .cargo/ +cp %{_builddir}/%{name}/service/attestation/attestation-agent/.cargo/config.toml .cargo/ %endif %build @@ -191,6 +228,8 @@ cd %{_builddir}/%{name}/service/attestation/attestation-agent/ %{_cargo} build --features virtcca-attester --bins --release cd %{_builddir}/%{name}/service/attestation/attestation-service/ %{_cargo} build --bins --release +cd %{_builddir}/%{name}/service/attestation/attestation-client/ +%{_cargo} build --bins --release %endif %install @@ -228,15 +267,19 @@ install -d %{buildroot}%{_sysconfdir}/attestation/attestation-service/token install -d %{buildroot}%{_sysconfdir}/attestation/attestation-service/policy install -d %{buildroot}%{_sysconfdir}/attestation/attestation-service/verifier/itrustee install -d %{buildroot}%{_sysconfdir}/attestation/attestation-service/verifier/virtcca +install -d %{buildroot}%{_sysconfdir}/attestation/attestation-service/resource/policy/oeas 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 644 service/attestation/attestation-types/src/resource/policy/opa/virtcca.rego %{buildroot}%{_sysconfdir}/attestation/attestation-service/resource/policy/oeas/ 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 @@ -270,7 +313,9 @@ popd %{_bindir}/* %exclude %{_bindir}/attestation-agent %exclude %{_bindir}/attestation-service +%exclude %{_bindir}/attestation-client %{_includedir}/secGear/* +%exclude %{_includedir}/secGear/ra_tls* %ifarch x86_64 %files sim @@ -291,13 +336,26 @@ popd %{_sysconfdir}/attestation/attestation-service/policy/* %{_sysconfdir}/attestation/attestation-service/verifier/itrustee %{_sysconfdir}/attestation/attestation-service/verifier/virtcca +%{_sysconfdir}/attestation/attestation-service/resource/policy/oeas/virtcca.rego + +%files ac +%{_bindir}/attestation-client %endif +%files ra_tls +%{_libdir}/libra_tls.so + +%files ra_tls-devel +%{_includedir}/secGear/ra_tls* + %post systemctl restart rsyslog %changelog +* Thu May 15 2025 huyubiao - 0.1.0-55 +- sync patches from upstream and sync with 25.03-0.1.0-59 + * Tue Mar 18 2025 xuraoqing - 0.1.0-54 - sync patches from upstream 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