Projects
Eulaceura:Factory
secGear
_service:obs_scm:0080-add-attestation-service.p...
Sign Up
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File _service:obs_scm:0080-add-attestation-service.patch of Package secGear
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
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.
浙ICP备2022010568号-2