secGear/0080-add-attestation-service.patch

4627 lines
173 KiB
Diff
Raw Normal View History

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