secGear/0080-add-attestation-service.patch
steven ygui 606e91387c backport patches
(cherry picked from commit 3beb59ff4b4e1ef8cd12e4afdee39d1a9081c37d)
2024-11-08 11:21:32 +08:00

4627 lines
173 KiB
Diff
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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": "eyJ0ZWUiOiJJdHJ1c3RlZSIsImV2aWRlbmNlIjoie1xuXHRcInJlcG9ydF9zaWduXCI6XHR7XG5cdFx0XCJzY2Vfbm9fYXNcIjpcdFwiQjJEUE1NbWRUT0lVN3FpNnFCc3NaOEhFN1gtRnlwQVF3Ml9zWUpjNVVoS0FIZlFUM3phZTM5cnN6TEFzaE5qOGJ5dEIyOHNTUnp2N3RXYmRPSmV3dW5Uc1pUNnJaSEFRWFFEc1k0UzloOFRIdDBPNnlnbUV6Z1MydjZkM0NpeW91MGtQanNVbzFFbHpxbU5KS0JTbFFpejNqQlVzTjZhVXo3dkM5Yllpd3FsdWpBZm9iUlBfT19OM193NGxfMmQ4T05OaWtWRHdGcE5zMjJqcVJ0ZzlxS2VvWkduZVhUSGQwYVIzMVNKTDhsRFJsOG5Ka0FLdkRFUHZ5Zl9GN0Jrd2pEYk5YM2hNdmJMLXFEWkVWT2JNdUcwYldBVGRJV0FUTDFMem9qbDRTUVNPZDNmMEc1VWg1QU9pMEJtcDZUT3ZVTG44c0FpMWkwenF3U3h3Y0U0dlJjSG56OEVwTndTcjdET2tmUXR3bkdCY21fZUZkcTYzYXAtaWN6ZWwxa2pZUFRHZXY0bEdpemt4Wm9VN3FfYTExUXJIc1dkYnppeDBaNHlpMnBWS21lUXB0TjNydmxIYXZzZXE5VTh5VXBwbkVoMnNhVzJ3QlJmS2hYSVIxRFhiTlpNOV9qUHdRNVRTanNGQXpKYTNzbWM5VkxUMlZQa2lKSzBtNzhLS19sNkQ4TVF4ZXMyU2Q1dG9fYS1hcHh1OEE2b1E5aVZXRzBkdS0xS05MUm1hbVRCcUpLZzRfQzh0Z041dUZ3ejRLMVZ2eEYtVjY5RWVEUXpRV0o0SWFQTFNCS3BzSkx1ZUZyQjk1TGNmWnhudk05OG5oQVo4QU5PQ3pFdXJSYlVlR1MwcDM2ZjUtU3BYSGlveTNSbm5rY05tYmlVb2cwbVd6T01HVTE4WTZjeFZJVGNcIlxuXHR9LFxuXHRcImFrY2VydFwiOlx0e1xuXHRcdFwic2NlX25vX2FzXCI6XHR7XG5cdFx0XHRcInNpZ25hdHVyZVwiOlx0e1xuXHRcdFx0XHRcImRya19jZXJ0XCI6XHRcIlRVbEpSV3RxUTBOQk0zRm5RWGRKUWtGblNWSkZVbU40Tnk5NE5sRkRSRnBCUmpGS2IxRkJhMkZGVFhkRVVWbEtTMjlhU1doMlkwNUJVVVZNUWxGQmQxQlVSVXhOUVd0SFFURlZSVUpvVFVOUk1EUjRSSHBCVGtKblRsWkNRVzlVUW10b01WbFlaR3hoVkVWa1RVSnpSMEV4VlVWQmVFMVZVMGhXYUdReVZuQkpSV3hWU1VaQ2VXSXlVakZaTTFGblVUQkZkMGhvWTA1TmFrMTNUbFJGTVUxVVRYbFBSRVY2VjJoalRrMTZaM2RPVkVWNFRWUk5lVTlFUlhwWGFrRTJUVkZ6ZDBOUldVUldVVkZIUlhkS1JGUnFSVkJOUVRCSFFURlZSVU5vVFVkVFNGWm9aREpXY0UxU2IzZEhRVmxFVmxGUlJFVjRSWGROYWxwSlZXdFplRTFGY3pWTlJFRTBUMVJGZUZaRVEwTkJhVWwzUkZGWlNrdHZXa2xvZG1OT1FWRkZRa0pSUVVSblowbFFRVVJEUTBGbmIwTm5aMGxDUVUxek9FTllRMXBvZGtObk1qbE1UbXRWWWxOTU9VbGljR3RaVkhSM1IyWTBLMGhYYjJoRUt6QnNPRVl3YVRGR1dIbEVibnBJYW1keU1UTnNWakp1YkZCR09XeHVVazlNWTJKSlRDdHRVVFJJYm05UVEyWXZZVXhSZFdsRFZUTkVjRXhNVG5aMFIyZFhlbXBoTW5CbFRIVlhPVWxWWkhoMGNuUlNRalV6YVdKSFRHeHdSVkl2ZEZkaVNUSk5SMmhTTldaWFptUXdSVGR4ZFM5Q1VGWnRPVlpKVDB4eFEyMVdaMWhWY0hSblVucDNPSFpIUkZSc09GSjFPRkJ4WWt0WmRucGhUbHBxYlhwek5XNUtPWGx5VW5KSksydFBaekI2TTBwWU1tTllRVzVEVVVneGVDOWlWRzlXV0dodFkybFpPVU5MTTNCR04ydDRWSGxsZUdoR1RqVXhZakp6VVdWNVEzTlhhazVsYnpsWlVGSTBTMUJ0VVVzNVJYWm5kbkpXYW5keVRuZFNMMkZwZDFKWlowbzJTelExUzNwS1FUSnNjWFZ0VmtReFUzcFlORmhtWm14RFNqRlphRkpTWTNWeWFFc3ZaRXA0UVZkUlJsa3JTV2Q0Y1V4SE9IRlFWbGM0U1RFMFpuZzJRbWxUYkZWelIzRlVRWFp5TjFWRlF6aFNWWEJWT0VsT2VUSkpaV3A1TlhOaWMyTnBSVlpsTkVkSGFqUk5PVTB2YzBaVFQzUmlOM1FyVm5GU1RsbFRTV05HYlhKVVZqVlNjbHBDVkZkU1kyeGhOM3BXVkc1NFUwNHdjR2x5UzBWWmNFOVBVV1p6UWxodU56VjNNMFpsU0RKcU5HdEpVV3RWYUVSNU9XcFhaa05sSzFONGVuQnZTMkpvVmxrd1dsbHNVRzl1U2s5QlowaEhWek5UU2tac2JpdG1Ra3BCZFdkc1RqbFphbVJ2UVZkcVZHcDRSRTB6ZFZWdGVUQjFkRFkxU20xME5uQkNkVU1yVVVwWFIySjFhQ3RFTUV0MFNVVnROVEZMVFM5RE1HUTRNME0yYm1GSk1qVmxla0o2T0ZGalpXbG5kRXg0U1UxYVprSkZWU3RYTm1aeU9HaHhWa1pDTldkRFIzVldiRzVUUTJSNmJFbDBlRXc0VldOeWVISmpRazVQWkRSMWNIZGxiMWt4ZEVOS1VUSTVXRGNyV0RjemRVVnNkVVpJUVdkTlFrRkJSMnBuV1RoM1oxbDNkMGgzV1VSV1VqQnFRa0puZDBadlFWVkZiMjh6TjFCc2VsWTVabXRuTjNFeFluZFFjek5oTkZKVWN6aDNRM2RaUkZaU01GQkNRVkZFUVdkUU5FMUdkMGREUTNOSFFWRlZSa0ozUlVKQ1JrRjNWR3BCYjBKblozSkNaMFZHUWxGamQwRnZXV05oU0ZJd1kwUnZka3g2UlhsT2VUUjNUR3BCZFUxVE9XcFpWMng2WXpOV2JFeHRhREJpVkVGcFFtZG5ja0puUlVaQ1VXTjNRVmxaVjJGSVVqQmpSRzkyVEhwRmVVNTVOSGRNYWtGMVRWUnZlVTFFVVRCTmVrRk9RbWRyY1docmFVYzVkekJDUVZGelJrRkJUME5CVVVWQlQzWlVXR1ZFWlZSVE1VNTVibEEzY0ZZMFYwdzBZMFp1UVdoaFNFbE5hbWhwYlZjM1RqbEZRblF3VTB4SFNGQXpaRGhzTTBsblYxSkZiMlpRTkZnd1JWSjFiR1J4WWxweFpucElSWEZhZG5abFZFOVNVVlZHYVdWV1ZURnJLekY0U1doRFUzbGtNMWhxVUdObGQxWkRiMFpDY0dNMlFURlFlbkJDY1dwM1kyTXpLMDFxTUdwcVNXaFpWMDAxVnl0b1VYRlJOVWhuT1RneWFWUmhNM2haTjBGc1UzaExVa2x2YkN0dU16SkpRM2hXTkUxbFQwdzNUa0ZoWVhkVlpGRkdOamh3ZHpGQk5tSmtXV2d5VjNWSWVHWXJaQ3RUWm5aVEwzZGxjVEJFV21rNGRUSnNURzFoVVZjd2JrcEVTVFUzZFcxWFEyTTNTa2d2VkZaR0wzSmlhaXRrZVhCUlJUSkJaa3h2VjNwS2JuUkRaa3d5UjI1eVlrZ3JXbkpPVHpSQ1l6RXpOWEZaU2xad1ZqTnNVSFpOYzFVeVozRk9jRFpvVmtndkt6UkJhRTQyWjFFNVYwbzFkbUprV25aT2VsZGpURlpHYzB4UlBUMFwiLFxuXHRcdFx0XHRcImRya19zaWduXCI6XHRcIkVTLUhLSUFyOTBYQ1h3ZXRfQi1pR1BmS0Uwa0VIdWczeGhnSUpRUWNRak5iSXc5bHJ4bFZaZE1XMnlIM3hfVlNMTUhfZ3RzWjVDSXRjYlpjNlVNaWhCeDRDcXVpdjZ5RG1yMlVOUmJrNGdKd2ZiNjE5em9pNkEzaThTcFpaaUctY2dQUlNGS05IdjNSV0tBbDdXaUd1SU04ZFlvdHR0eHhJT3U0bElrQWlXR2txRlpCUzJCc2JSNXVLdEl2R0hEaG1QZEh5c3JwU3lrb0loVE4wM0FpY0NNYWRFVnhzbUs5Y0pGRlFTOHg3ZlAxU0VMUHJDMFdUT0F4bjlNdFBRVXcwSnV5dzZKeXVJZU5BYjRYRV9uNjZQOU9oRXV5Y3RjajBsa3RxLUpneFFlalRVejFROHVTbVFEV2ROcUZlRTFOQWk3Zmd6YVNjd2tTc3lXa0dtckt0MFZaSnI2a0hCT0xYMFNEMElPRWJROFNRdHFieVRpRko4Uk4tMXZqb05LYjhobnpIUnpqeGJ4eGYtVW9zeWlhY3RNUVRnOUNlTi1rSVRjUDJTUVE4ZjA0OThVVkpRamFoU05OeVcxdnp6c0hXaFQyeWMtcFdwSll6YUtrXzBjdFBVT3UwazJ1VS1EbENOdFQzRkVpS0hMZWVzRmdUSUlmdFJ1Z1JoMklHSFVkcFlvTmJDVmxvRXdaLWRPY19fQ212ZTBOOVVoWWdSUUpER1VhOGRWZVhKVXZXZ2dNQXpJdEE0QUhhVlByTFRFSTRHQld5bEw5Wm1FclU5bmpXZjd3X09XcldvaUFlVm5KMl9yanhpYjVUZ3ZldXJCM3pCbEJMSTVwQ0ExbmxhT0N0aUlMM0c1dEs3N3NNXzNXeUhYSG96cFRTeTR3WG5mU0tGeHUweXROV240XCJcblx0XHRcdH0sXG5cdFx0XHRcInBheWxvYWRcIjpcdHtcblx0XHRcdFx0XCJ2ZXJzaW9uXCI6XHRcIlRFRS5SQS4xLjBcIixcblx0XHRcdFx0XCJ0aW1lc3RhbXBcIjpcdFwiOTU1ODA5NDE0MTIzNTRcIixcblx0XHRcdFx0XCJzY2VuYXJpb1wiOlx0XCJzY2Vfbm9fYXNcIixcblx0XHRcdFx0XCJzaWduX2FsZ1wiOlx0XCJQUzI1NlwiLFxuXHRcdFx0XHRcImhhc2hfYWxnXCI6XHRcIkhTMjU2XCIsXG5cdFx0XHRcdFwicXRhX2ltZ1wiOlx0XCI3V2N1ZjZLMEM0XzBUTVIwZEMyMURBdVZ2c0c2WEIySjhxZ0dtUUFHSVdzXCIsXG5cdFx0XHRcdFwicXRhX21lbVwiOlx0XCJQVXJNdENoSGRzaW1VQWZrRnZrc3Y1Z3ZHMzFSalgtbF8wb0JsLUpCZHNNXCIsXG5cdFx0XHRcdFwidGNiXCI6XHRcIlwiLFxuXHRcdFx0XHRcImFrX3B1YlwiOlx0e1xuXHRcdFx0XHRcdFwia3R5XCI6XHRcIlJTQVwiLFxuXHRcdFx0XHRcdFwiblwiOlx0XCJwUkU1OGtzdW9IeGNMMExiMWs1blNfZkt2eERZSm96anhnYlh0aVU5T0RJNFMxa1hSZ2ZBNWdiRi1BQVJkSDZZOTdzQ0t6TklBNFRZd0pVTWh0ZFlScTZZYWZrSFZLb29tUWhJYU5mSUFaMG1BWXU5V2ttS2hBNE9BRjJNX0xDMW03WVVGMlI1S2taaGxzeFUtMEo3bWwyM2Q3RFB1enI1cnJwREFDYWxqMmZtLTROVk5DMHdYTjdPaWVXSW9UdFF0Sk9DSW5aODFjZ3FWZUxhUEVUSnpmRnBtX0ZBWXgtWDlsVnlrUFVxeWNuZnVFZjJ1Tml2OVNyMHc1d1NlX1BSRmlNQmRCRGxSQllqODBsbHllbGZGb0tWeE41c1BXMENuRkJZdlA2MHV6VVNSMDZjbHZxSU4wWUotTnhEcVhMU1VrajAtZEluNHdFLTVaaU55azI1Q25mWE5tMDk1c2tOeFo4SUxYN3FIZEJkVFhKczYxQ3J1TllsUWg0S2pHZVpWZkxVWkJZaVIwZC1JbjMyWDBYakQ1QWw4OHhLNFdxWFNhdFF6N01MekhBLURKYU8ybHNKZEFxTGFqdGxnV2ZOYXBWbWd5cVB1OFFVTkhfaEhqQlhTUDRNMXEwTUFTd2JCWXlib1VVMl9CY0lSZ0pwTnNneHlRNTJibTU2aUppYVFRQ05rYU55R1FhajRxWEVodjFTQlFlYjlMSVR1dVZfRDQzLUdhS2Z3dEhWZEZiYXdvYjZFbm5yWHoxVW1fREt1dXhLU09QZ3E0MEpRanFCQllMa1J2cXJfbS1ES3BManlVb2w4MS1ldUU1bjNZejFXU1lIbG5tc1hfMEYzbFZDQktQelR5aWd5SGZvZ0hjVXNtX2tTVDFfbWdIajg2ZnZ2OVJGTFFaTFBJRVwiLFxuXHRcdFx0XHRcdFwiZVwiOlx0XCJBUUFCXCJcblx0XHRcdFx0fVxuXHRcdFx0fSxcblx0XHRcdFwiaGFuZGxlclwiOlx0XCJwcm92aXNpb25pbmctb3V0cHV0XCJcblx0XHR9XG5cdH0sXG5cdFwicGF5bG9hZFwiOlx0e1xuXHRcdFwidmVyc2lvblwiOlx0XCJURUUuUkEuMS4wXCIsXG5cdFx0XCJ0aW1lc3RhbXBcIjpcdFwiOTU1ODExNzg4NzA1MzRcIixcblx0XHRcIm5vbmNlXCI6XHRcIjcxb1ppbEF5NnZYQ2dGdVJVaEFZTkFcIixcblx0XHRcImtleVwiOlx0e1xuXHRcdFx0XCJwdWJfa2V5XCI6XHR7XG5cdFx0XHRcdFwiblwiOlx0XCJ1LUtoY1RGLTRMdVhzTUtJZ0I0a0hDMG9NbVZXOV9OUGFGRlB0ajRfSG15azd0b0psS09BMVFENFRiY0dGYUdzenk3aUhvUm9hSUphMnJ6djFuUm1tZ3JlN0lRbTZaSXN2Z3M2bm80OW4ybUJtcENOVDlacEtXemJEMjN3N2hya3VUdkY5LUlTalhMRDk4Tk1ycF9OdUxXalNCczJPV3hyWGJrdUdjU1pETXN5NjJ6dkYxclhIYXFRRmFKNXhpU3cyaGU3YS01YWFfUGpuQmtMa0VYOE1ySF9RSzBtUFI1cmFNeU85a25CVU9UT0owdUpVS3hIQ2dKSEpTU051aDY1ZUt4VlJCS3pKbzNrT0d3Z1lRdjltOEJsZUhjeWoyWlZBVUhfZFFrajRZZFQ3bjAyZ3VBam9RMVR4aHl6R250RnV1VTFTVUZDRVAwZzN3dmI5c2N4UVNmZlNacjl5WTlCMElBR1REckZ3Q0dPM3JUamF3NXJxY2FxSlprY0pkMjFIU05DNTJUOEdENHo4UWhfV05iUHNueV9sRXI5VzU5ZHBXN0FjWlJvSkZlMHhiOGhSaURwRldmOTZJdnBTT2lQZ2RNQVY5SHRjeFRoOS1pdzJiYl9kQzYtekVLS2xfTUtzWE1rSk1DdmxDazdCd3dQX1ZKVnQ2OS1pLVo0dWN3YXhOQUxCdHJfZWFQU0RPNHU2d2JDNUl1bGlPb2VvM0hEci1FQ3RwWVdWWVplVXBGTUIwZF9GU3hsT3NXQjE3QzRNT3ZzNjVtQWcwYXJNOTMyb00xeE5WUWxfYzZkS1g4alVzV1FubXpDcG5zS1JCU3UwRHFwU0JORjhNSnY4ZDR2cFRoNFU2NmhHWFhRYjVWZjdDWkpCbC05dFZRMGJHQTVhNjFfbkYxRXhtc1wiLFxuXHRcdFx0XHRcImVcIjpcdFwiQVFBQlwiLFxuXHRcdFx0XHRcImt0eVwiOlx0XCJSU0FcIlxuXHRcdFx0fSxcblx0XHRcdFwiZW5jX2tleVwiOlx0XCJWa3hVX1FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBakJBQUFBQUFBQUNBQUFBQUFBQUFBREFBQUFBQUFBQUFNQUFBQUFBQUFBQkFBQUFBQUFBQUFFQUFBQUFBQUFBRHQ1TnJxQUpocjE1N0Q5V0QyTDRCdE9nVnlXejJfQzZTLXNLc2lzb0xpakRfWmtGM29GenpnRHV1MG04XzNSRk9oc2xTV1A4MTNJTVB3SUZMRDN4MkhSOG9VbkUyYS1fT2UwY1diRGZrQUtHLUxKaS0zNzFoNWs4T25lMHV0UmhyV1lTRnBCUS1lWGlkdlJEcUg3NUFpcDlNemJ5TExYTUlvbm1LWXlXa3EzczJMWVRiX1g1UkhMSnZqVjd0cDZxS20ySnl3TzY3TUdiVGdTUzJsbzc1SGJIaVQ5dmFBZldaUFF5Y1FDMHROTlhfRUJfUUYyTWJBckpibi1UM3NaTVhya195X3RDempMYTNWZWJXS2lsUGV5aXpYWFFBb1cyc0Q5MGZybEZYazhwMU9lTGhxcWxLVml3di1rUlk0ZU5ISnJXOGVTdTZmQndLUGw4dWJUcGt3eV9ickprYmFXUVVoYVk5ZFN5Wml5bWhPZHFTTGw4dTRwRnZvd0xKdDJQanpWajhDRTNQM0M4OEtSMkdsNjRvaUF3dnk2OWVsT1E3aXRXa1VjLTZlUDR2X0paT1VOcVZUN2hCUzNKWEJ4MmxjeS0yc0lEc1FhMGhSSW10eERFZXhoendWUFJrM2xwQkFwSlljcTR6LUVHWGV6dm5Kb0NERVRHeHVMSHl3MkctRV9lY1FnTTlyVTVQOW9GTG5fSm96OFBBYkJ6ZzgwOURwc0lKM051Slp3ZkhoNkpyNHlRRXZvTHphTFhsa1VVS0IxQXhjWXphLV9tT0R6WEJnRXRHRnBpWk9UYkFoRUhSWXRzQ25paUpDbTR6Mk9nay1HY1YwdVl1cTlCZDhreGIyVnU2NTdqVG1XVGxPVVE5SnZ1bXZ4Mks3MnJyakZBdzRpVy1PSUFab2JpdmVoN1RtUjgwaFdQaThOcTlBd1BGYm9zVWZwWDUzeUNZWUNpSk15ZzUwRUpQZXBwaFduS2NReERGNUNZMjBXSkpEXzN5REJ6WEFOeDdJSmE3NUVGMGRRVm9BSXEtVW1nODJHZWMzdWdrUEtTTXl0RHZNMmZUVlRPMl9QYlJETkFlbFpoZ3VEX0hZMDhhRTVybVE0NTFPLVFlR3pnbWw3QWpNaHJBTVZwQlp1VEpneFBNRGpWaTFWZHZ2ZzlaLWVobTlwYzFENzRCQ1hwdEZFUEVFRHhjd1dvaEhkLXExVTMtczZQTVlRbUs4QzJEQ25zekUzZ0xueTZJQW9PZ19zSGJUT1Jha1dYWnlhZkNMelJ4NmxCaFlyNkZ4OHo1Y1BIOXQ1VEstcmVqVjdPTURZbWVaUFl5QmFCbE5NYzNRbVo4Tk94d0Z2bWN1MnhEMVU4cjZqSXRqR3BVdUN5RDEzRGZnZ0REVmZWem9HSldXVHROMXVLWkNfRHVQdi12blRXdFIwZjQ4cTd6dDY1WXVpVC04NFptUXQ0Q1NMTlZNb2plR2lDbXN6bVZ1Y1ExSENxcklsQVBtM243UTJDSmx6ZEdOUWZyV3FJNmU3VHo2VGg2UWVBUkZCYzM5VVI4RjlMaHpDc2dYcGI1NWRTb0E5UXVDTEswdzRWNnFjWUE5QmJkZzRlb1ZRV1J1V1ljTDlKMl92TGZON3ROSWpCa01rTmhmZDZleXk4M2JrUmpuY3p5UFh6UllKUXF2RkppN3Y3ZjhtS0lSV1VkemNPSzhDOFB6UkJvdFR6SXZqdm9XWkpfcW45OHhWVlRJTU1pYWhZRF9ldHE5cjVTeWpTR2ZHM25nVzllblJ6a2FrVDdIc1V0MjYyalpWekxITzJKUGN0X0h2T1RkVFNWeW9wZjFUYUZlY1dCSHdaMVA2NUZpcXFueFhZam9XVnZyWlc1dmg1UGgtUzBKZ2V5U0Z1NndBWldwTkhEbWtNOGhvR09PVzMzRDZUaTFYeXY1VGpvYTI3ZmlZdGdZd3RWUTBHb2RodTBYN3NxN0Y4UnVjWW55M05yYUtJSXpRaUp4ZHRlSGNzQnpxUTRDWk5JSlNuMFZzellVQkRxR0t1Y0RmeVhVXCJcblx0XHR9LFxuXHRcdFwic2NlbmFyaW9cIjpcdFwic2NlX25vX2FzXCIsXG5cdFx0XCJ1dWlkXCI6XHRcImY2OGZkNzA0LTZlYjEtNGQxNC1iMjE4LTcyMjg1MGViM2VmMFwiLFxuXHRcdFwicmVxdWVzdGVyX3V1aWRcIjpcdFwiXCIsXG5cdFx0XCJoYXNoX2FsZ1wiOlx0XCJIUzI1NlwiLFxuXHRcdFwic2lnbl9hbGdcIjpcdFwiUFMyNTZcIixcblx0XHRcInRhX21lbVwiOlx0XCJRYjkyZWUxTlRnZlVpdnlISXZuaEVZcU5IU0tLS0FPVUlzV1Y5dVl0eXA4XCIsXG5cdFx0XCJ0YV9pbWdcIjpcdFwiQW1VLXI4OHREOVhVSlMwOGhkVkJTREFySDE0WnI3UXFRQlpwLXp2WkE5a1wiLFxuXHRcdFwidGFfYXR0clwiOlx0XCJcIixcblx0XHRcInRjYlwiOlx0XCJcIlxuXHR9LFxuXHRcImhhbmRsZXJcIjpcdFwicmVwb3J0LW91dHB1dFwiXG59In0",
+ }
+ );
+ let client = Client::new();
+ let endpoint = "http://127.0.0.1:8080/attestation";
+ let res = client
+ .post(endpoint)
+ .header("Content-Type", "application/json")
+ .body(request_body.to_string())
+ .send()
+ .unwrap();
+ assert_eq!(res.status(), reqwest::StatusCode::OK);
+ println!("{:?}", res.text().unwrap());
+ }
+
+ #[test]
+ fn api_get_challenge() {
+ let client: Client = Client::new();
+ let endpoint = "http://127.0.0.1:8080/challenge";
+ let res = client
+ .get(endpoint)
+ .send()
+ .unwrap();
+ assert_eq!(res.status(), reqwest::StatusCode::OK);
+ println!("{:?}", res.text().unwrap());
+ }
+
+}
diff --git a/service/attestation/attestation-service/token/Cargo.toml b/service/attestation/attestation-service/token/Cargo.toml
new file mode 100644
index 0000000..c4b885c
--- /dev/null
+++ b/service/attestation/attestation-service/token/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "token_signer"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+jsonwebtoken.workspace = true
+serde.workspace = true
+serde_json.workspace = true
+anyhow.workspace = true
+attestation-types.workspace = true
\ No newline at end of file
diff --git a/service/attestation/attestation-service/token/src/lib.rs b/service/attestation/attestation-service/token/src/lib.rs
new file mode 100644
index 0000000..ed41a4e
--- /dev/null
+++ b/service/attestation/attestation-service/token/src/lib.rs
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved.
+ * secGear is licensed under the Mulan PSL v2.
+ * You can use this software according to the terms and conditions of the Mulan PSL v2.
+ * You may obtain a copy of Mulan PSL v2 at:
+ * http://license.coscl.org.cn/MulanPSL2
+ * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
+ * PURPOSE.
+ * See the Mulan PSL v2 for more details.
+ */
+use anyhow::{Result, bail};
+use jsonwebtoken::{encode, get_current_timestamp,
+ Algorithm, EncodingKey, Header,
+};
+use std::path::Path;
+use serde::{Deserialize, Serialize};
+use serde_json::Value;
+use attestation_types::{EvlResult, Claims};
+
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct TokenSignConfig {
+ pub iss: String,
+ pub nbf: usize, // 生效时刻
+ pub valid_duration: usize, // 有效时间
+ pub alg: SignAlg,
+ pub key: String,
+}
+
+impl Default for TokenSignConfig {
+ fn default() -> Self {
+ TokenSignConfig {
+ iss: "oeas".to_string(),
+ nbf: 0,
+ valid_duration: 300,
+ alg: SignAlg::PS256,
+ key: "/etc/attestation/attestation-service/token/private.pem".to_string(),
+ }
+ }
+}
+
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct EvlReport {
+ pub tee: String,
+ pub result: EvlResult,
+ pub tcb_status: Value,
+}
+
+pub type SignAlg = Algorithm;
+pub struct TokenSigner {
+ pub config: TokenSignConfig,
+}
+
+impl Default for TokenSigner {
+ fn default() -> Self {
+ TokenSigner {
+ config: TokenSignConfig::default(),
+ }
+ }
+}
+
+impl TokenSigner {
+ pub fn new(config: TokenSignConfig) -> Result<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