From d06b6beab9ae13898870297e8ef2ae806cd8d6d0 Mon Sep 17 00:00:00 2001 From: houmingyong Date: Tue, 28 May 2024 10:25:41 +0800 Subject: [PATCH] init attestation The current patch incorporates the following commit points: Reference: https://gitee.com/openeuler/secGear/commit/d06b6beab9ae13898870297e8ef2ae806cd8d6d0 https://gitee.com/openeuler/secGear/commit/b90e039631f1031a485ef038174c0bef831223a5 https://gitee.com/openeuler/secGear/commit/dad056809c5e94b50c47063d728d5f1e47800512 https://gitee.com/openeuler/secGear/commit/ce4c7b6a8c013cd208004a3cec13a15fff100b1a https://gitee.com/openeuler/secGear/commit/8e02b257d9bec81bc557d6431e90448522ad6270 https://gitee.com/openeuler/secGear/commit/980d0a89d3b1b1a6d280846d6edddabdfd57a635 https://gitee.com/openeuler/secGear/commit/1fbc825bd34e859f3bc641f6b1b14c106be23433 https://gitee.com/openeuler/secGear/commit/97f78a21040443796d137ce1739861b66451c7dd Conflict:no --- .../attestation/attestation-agent/Cargo.toml | 30 ++ .../attestation/attestation-agent/README.md | 5 + .../attestation-agent/agent/Cargo.toml | 38 ++ .../agent/attestation-agent.example.toml | 1 + .../attestation-agent/agent/src/agent.rs | 144 +++++++ .../agent/src/bin/aa-test/main.rs | 68 ++++ .../agent/src/bin/generate-headers/main.rs | 4 + .../attestation-agent/agent/src/lib.rs | 84 ++++ .../attestation-agent/attester/Cargo.toml | 24 ++ .../attester/src/itrustee/itrustee.rs | 51 +++ .../attester/src/itrustee/mod.rs | 130 ++++++ .../attestation-agent/attester/src/lib.rs | 90 +++++ .../attester/src/virtcca/mod.rs | 88 +++++ .../attester/src/virtcca/virtcca.rs | 108 +++++ .../rust_attestation_agent.h | 56 +++ .../attestation-service/Cargo.toml | 22 ++ .../attestation-service/verifier/Cargo.toml | 27 ++ .../verifier/src/itrustee/itrustee.rs | 53 +++ .../verifier/src/itrustee/mod.rs | 58 +++ .../attestation-service/verifier/src/lib.rs | 51 +++ .../verifier/src/virtcca/mod.rs | 373 ++++++++++++++++++ 21 files changed, 1505 insertions(+) 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.example.toml create mode 100644 service/attestation/attestation-agent/agent/src/agent.rs 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/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/rust_attestation_agent.h create mode 100644 service/attestation/attestation-service/Cargo.toml 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/mod.rs diff --git a/service/attestation/attestation-agent/Cargo.toml b/service/attestation/attestation-agent/Cargo.toml new file mode 100644 index 0000000..7a424e6 --- /dev/null +++ b/service/attestation/attestation-agent/Cargo.toml @@ -0,0 +1,30 @@ +[workspace] +resolver = "2" +members = [ + "agent", + "attester", +] + +[workspace.dependencies] +serde = "=1.0.203" +half = "=1.6" +ciborium-ll = "=0.2.0" +ciborium-io = "=0.2.0" +ciborium = "=0.2.0" +anyhow = "1.0" +serde_json = "1.0" +async-trait = "=0.1.78" +cose-rust = "=0.1.7" +hex = "0.4" +openssl = "=0.10.64" +log = "=0.4.14" + +config = "=0.13.0" +rand = "=0.8.5" +base64-url = "=3.0.0" +tokio = "1.0" +env_logger = "=0.4.0" +safer-ffi = "=0.1.8" +futures = "=0.3.30" + +verifier = {path = "../attestation-service/verifier", default-features = false} 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..66919d9 --- /dev/null +++ b/service/attestation/attestation-agent/agent/Cargo.toml @@ -0,0 +1,42 @@ +[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 = [] +with_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 + +attester = { path = "../attester" } +verifier = { workspace = true, optional = true } diff --git a/service/attestation/attestation-agent/agent/attestation-agent.example.toml b/service/attestation/attestation-agent/agent/attestation-agent.example.toml new file mode 100644 index 0000000..4ba5ef2 --- /dev/null +++ b/service/attestation/attestation-agent/agent/attestation-agent.example.toml @@ -0,0 +1 @@ +svr_url = "http://127.0.0.1:8888" diff --git a/service/attestation/attestation-agent/agent/src/agent.rs b/service/attestation/attestation-agent/agent/src/agent.rs new file mode 100644 index 0000000..9aec2bb --- /dev/null +++ b/service/attestation/attestation-agent/agent/src/agent.rs @@ -0,0 +1,145 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * secGear is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +//! 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; +use log; +use serde::{Serialize, Deserialize}; +use async_trait::async_trait; + +use attester::{Attester, AttesterAPIs}; + +#[cfg(feature = "no_as")] +use verifier::{Verifier, VerifierAPIs}; + +const DEFAULT_AA_CONF_PATH: &str = "/etc/attestation/attestation-agent.toml"; + +pub use attester::EvidenceRequest; + +#[async_trait] +pub trait AttestationAgentAPIs { + /// `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>; + + /// `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]) -> Result<()>; +} + +#[async_trait] +impl AttestationAgentAPIs for AttestationAgent { + async fn get_evidence(&self, user_data: EvidenceRequest) -> Result> { + Attester::default().tee_get_evidence(user_data).await + } + async fn verify_evidence(&self, challenge: &[u8], evidence: &[u8]) -> Result<()> { + #[cfg(feature = "no_as")] + let _ret = Verifier::default().verify_evidence(challenge, evidence).await?; + #[cfg(feature = "with_as")] + let _ret = request_as(challenge, evidence); + + return Ok(_ret); + } +} + +fn request_as(challenge: &[u8], evidence: &[u8]) -> Result<()> { + let _ = challenge; + let _ = evidence; + // todo send request to attestation service + Ok(()) +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +struct AAConfig { + svr_url: String, // Attestation Service url +} + +impl Default for AAConfig { + fn default() -> Self { + Self { + svr_url: String::from(""), + } + } +} + +impl TryFrom<&str> for AAConfig { + type Error = config::ConfigError; + fn try_from(config_path: &str) -> Result { + let c = config::Config::builder() + .add_source(config::File::with_name(config_path)) + .build()?; + let cfg = c.try_deserialize()?; + Ok(cfg) + } +} + +#[derive(Debug)] +pub struct AttestationAgent { + _config: AAConfig, +} + +impl Default for AttestationAgent { + fn default() -> Self { + if let Ok(_config) = AAConfig::try_from(DEFAULT_AA_CONF_PATH) { + log::info!("attestation agent construct with default config file"); + return AttestationAgent { _config }; + } + log::warn!("The default conf file {} is missing", DEFAULT_AA_CONF_PATH); + Self { + _config: AAConfig::default(), + } + } +} + +#[allow(dead_code)] +impl AttestationAgent { + pub fn new(conf_path: Option<&str>) -> Result { + let _config = match conf_path { + Some(conf_path) => { + log::info!("Attestation Agent config file:{conf_path}"); + AAConfig::try_from(conf_path)? + } + None => { + log::warn!("No Attestation Agent config file specified. Using a default config"); + AAConfig::default() + } + }; + Ok(AttestationAgent {_config}) + } +} + +#[cfg(test)] +mod tests { + use crate::agent::*; + + #[test] + fn aa_default_conf_file() { + let aa = AttestationAgent::default(); + assert_eq!(aa._config.svr_url, "http://127.0.0.1:8000"); + } + + #[test] + fn aa_new_no_conf_path() { + let aa = AttestationAgent::new(None).unwrap(); + assert_eq!(aa._config.svr_url, ""); + } + + #[test] + fn aa_new_with_example_conf() { + let aa = AttestationAgent::new(Some("attestation-agent.example.toml")).unwrap(); + assert_eq!(aa._config.svr_url, "http://127.0.0.1:8888"); + } +} 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..5cedfc8 --- /dev/null +++ b/service/attestation/attestation-agent/agent/src/bin/aa-test/main.rs @@ -0,0 +1,68 @@ +/* + * 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 rand::{self, RngCore}; +use tokio; +use env_logger; + +const TEST_THREAD_NUM: i64 = 1; // multi thread num + +//mod agent; +use attestation_agent::agent::*; + +#[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 {attestation_proc(i).await;}); + handles.push(t); + } + + for handle in handles { + let _ = tokio::join!(handle); + } + println!("main stop"); +} + +async fn attestation_proc(i: i64) { + println!("attestation_proc {} start", i); + let aa: AttestationAgent = AttestationAgent::default(); + + let mut nonce: [u8; 16] = [0; 16]; + rand::thread_rng().fill_bytes(&mut nonce); + + // Step1: construct input param + let user_data: attester::EvidenceRequest = attester::EvidenceRequest { + uuid: String::from("f68fd704-6eb1-4d14-b218-722850eb3ef0"), + challenge: nonce.to_vec(), + }; + + // Step2: get tee evidence + let evidence = aa.get_evidence(user_data).await; + match evidence { + Ok(evidence) => { + println!("get evidence success"); + // Step3: verify evidence + let ret = aa.verify_evidence(&nonce, &evidence).await; + match ret { + Ok(_) => println!("verify evidence success"), + Err(_) =>println!("verify evidence failed"), + } + }, + Err(e) => println!("get evidence failed: {}", e), + } + 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..3eb4334 --- /dev/null +++ b/service/attestation/attestation-agent/agent/src/bin/generate-headers/main.rs @@ -0,0 +1,4 @@ +use attestation_agent; +fn main() -> ::std::io::Result<()> { + attestation_agent::generate_headers() +} \ No newline at end of file diff --git a/service/attestation/attestation-agent/agent/src/lib.rs b/service/attestation/attestation-agent/agent/src/lib.rs new file mode 100644 index 0000000..0f1efc2 --- /dev/null +++ b/service/attestation/attestation-agent/agent/src/lib.rs @@ -0,0 +1,73 @@ +use agent::*; +pub mod agent; + +// c interface +use safer_ffi::prelude::*; +use futures::executor::block_on; +use attester::EvidenceRequest; + +#[ffi_export] +pub fn get_report(c_uuid: Option<&repr_c::String>, c_challenge: Option<&repr_c::Vec>) -> repr_c::Vec { + let uuid = match c_uuid { + None => {println!("uuid is null"); return Vec::new().into();}, + Some(uuid) => uuid.clone().to_string(), + }; + let challenge = match c_challenge { + None => {println!("challenge is null"); return Vec::new().into();}, + Some(cha) => cha.clone().to_vec(), + }; + + let input: EvidenceRequest = EvidenceRequest { + uuid: uuid, + challenge: challenge, + }; + + let fut = async { + agent::AttestationAgent::default().get_evidence(input).await + }; + let report: Vec = match block_on(fut) { + Ok(report) => report, + Err(e) => { + println!("get report failed {:?}", e); + Vec::new() + }, + }; + + report.into() +} + +#[ffi_export] +pub fn verify_report(c_challenge: Option<&repr_c::Vec>, report: Option<&repr_c::Vec>) -> safer_ffi::libc::c_int { + let challenge = match c_challenge { + None => {println!("challenge is null"); return 1;}, + Some(cha) => cha.clone().to_vec(), + }; + + let report = match report { + None => {println!("report is null"); return 1;}, + Some(report) => report.clone().to_vec(), + }; + + let fut = async {agent::AttestationAgent::default().verify_evidence( + &challenge, &report).await}; + let ret = match block_on(fut) { + Ok(_) => return 0, + Err(e) => { + println!("verify report failed {:?}", e); + return 1; + } + }; +} + +#[ffi_export] +pub fn free_report(report: repr_c::Vec) { + drop(report); +} + +// 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("rust_attestation_agent.h")? + .generate() +} \ No newline at end of file diff --git a/service/attestation/attestation-agent/attester/Cargo.toml b/service/attestation/attestation-agent/attester/Cargo.toml new file mode 100644 index 0000000..575e63f --- /dev/null +++ b/service/attestation/attestation-agent/attester/Cargo.toml @@ -0,0 +1,24 @@ +[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 + +[dev-dependencies] + + + + + 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 = ::std::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::std::mem::size_of::(), + 16usize, + concat!("Size of: ", stringify!(ra_buffer_data)) + ); + assert_eq!( + ::std::mem::align_of::(), + 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 { + 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 { + 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..28bf33c --- /dev/null +++ b/service/attestation/attestation-agent/attester/src/lib.rs @@ -0,0 +1,90 @@ +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved. + * secGear is licensed under the Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * http://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR + * PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +//! attester +//! +//! This crate provides unified APIs to get TEE evidence. + +use anyhow::*; +use serde::{Serialize, Deserialize}; +use async_trait::async_trait; +use log; + +#[cfg(feature = "itrustee-attester")] +mod itrustee; + +#[cfg(feature = "virtcca-attester")] +pub mod virtcca; + +#[derive(Debug)] +pub struct EvidenceRequest { + pub uuid: String, + pub challenge: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Evidence { + pub tee: TeeType, + pub evidence: String, +} + +#[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>; +} + +#[derive(Default)] +pub struct Attester {} + +#[derive(Debug, Serialize, Deserialize)] +pub enum TeeType { + Itrustee = 1, + Virtcca, + Invalid, +} + +const MAX_CHALLENGE_LEN: usize = 64; + +#[async_trait] +impl AttesterAPIs for Attester { + async fn tee_get_evidence(&self, _user_data: EvidenceRequest) -> Result> { + let len = _user_data.challenge.len(); + if len <= 0 || len > MAX_CHALLENGE_LEN { + log::error!("challenge len is error, expecting 0 < len <= {}, got {}", MAX_CHALLENGE_LEN, len); + bail!("challenge len is error, expecting 0 < len <= {}, got {}", MAX_CHALLENGE_LEN, len); + } + #[cfg(feature = "itrustee-attester")] + if itrustee::detect_platform() { + let evidence = itrustee::ItrusteeAttester::default().tee_get_evidence(_user_data).await?; + let aa_evidence = Evidence { + tee: TeeType::Itrustee, + 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..dfb3aef --- /dev/null +++ b/service/attestation/attestation-agent/attester/src/virtcca/mod.rs @@ -0,0 +1,88 @@ +/* + * 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 serde::{Serialize, Deserialize}; +use log; + +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 { + let evidence = virtcca_get_token(&user_data.challenge)?; + let evidence = serde_json::to_string(&evidence)?; + Ok(evidence) + } +} + +pub fn detect_platform() -> bool { + Path::new("/dev/tsi").exists() +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct VirtccaEvidence { + pub evidence: Vec, + pub dev_cert: Vec, +} + +fn virtcca_get_token(challenge: &[u8]) -> Result { + unsafe { + let ctx = tsi_new_ctx(); + + let mut challenge = 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 evidence = VirtccaEvidence { + evidence: token, + dev_cert: dev_cert, + }; + 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..65da1d8 --- /dev/null +++ b/service/attestation/attestation-agent/attester/src/virtcca/virtcca.rs @@ -0,0 +1,108 @@ +/* automatically generated by rust-bindgen 0.69.4 */ +#[allow(non_camel_case_types)] +pub type wchar_t = ::std::os::raw::c_int; +#[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 = ::std::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::std::mem::size_of::(), + 32usize, + concat!("Size of: ", stringify!(max_align_t)) + ); + assert_eq!( + ::std::mem::align_of::(), + 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 = ::std::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::std::mem::size_of::(), + 4usize, + concat!("Size of: ", stringify!(tsi_ctx)) + ); + assert_eq!( + ::std::mem::align_of::(), + 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/rust_attestation_agent.h b/service/attestation/attestation-agent/rust_attestation_agent.h new file mode 100644 index 0000000..e06e61e --- /dev/null +++ b/service/attestation/attestation-agent/rust_attestation_agent.h @@ -0,0 +1,56 @@ +/*! \file */ +/******************************************* + * * + * File auto-generated by `::safer_ffi`. * + * * + * Do not manually edit this file. * + * * + *******************************************/ + +#ifndef __RUST_ATTESTATION_AGENT__ +#define __RUST_ATTESTATION_AGENT__ +#ifdef __cplusplus +extern "C" { +#endif + + +#include +#include + +/** \brief + * Same as [`Vec`][`rust::Vec`], but with guaranteed `#[repr(C)]` layout + */ +typedef struct Vec_uint8 { + /** */ + uint8_t * ptr; + + /** */ + size_t len; + + /** */ + size_t cap; +} Vec_uint8_t; + +/** */ +void +free_report ( + Vec_uint8_t report); + +/** */ +Vec_uint8_t +get_report ( + Vec_uint8_t const * c_uuid, + Vec_uint8_t const * c_challenge); + +/** */ +int32_t +verify_report ( + Vec_uint8_t const * c_challenge, + Vec_uint8_t const * report); + + +#ifdef __cplusplus +} /* extern \"C\" */ +#endif + +#endif /* __RUST_ATTESTATION_AGENT__ */ diff --git a/service/attestation/attestation-service/Cargo.toml b/service/attestation/attestation-service/Cargo.toml new file mode 100644 index 0000000..e244637 --- /dev/null +++ b/service/attestation/attestation-service/Cargo.toml @@ -0,0 +1,22 @@ +[workspace] +resolver = "2" +members = [ + #"service", + "verifier", +] + +[workspace.dependencies] +serde = "=1.0.203" +half = "=1.6" +ciborium-ll = "=0.2.0" +ciborium-io = "=0.2.0" +ciborium = "=0.2.0" +anyhow = "1.0" +serde_json = "1.0" +async-trait = "=0.1.78" +cose-rust = "=0.1.7" +hex = "0.4" +openssl = "=0.10.64" +log = "=0.4.14" + +attester = {path = "../attestation-agent/attester"} diff --git a/service/attestation/attestation-service/verifier/Cargo.toml b/service/attestation/attestation-service/verifier/Cargo.toml new file mode 100644 index 0000000..d5f2874 --- /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 +ciborium-ll.workspace = true +ciborium-io.workspace = true +ciborium.workspace = true +half.workspace = true +serde.workspace = true +serde_json.workspace = true +async-trait.workspace = true +cose-rust.workspace = true +hex.workspace = true +openssl.workspace = true +log.workspace = true + +attester.workspace = true + +[dev-dependencies] + +[features] +default = [ "itrustee-verifier","virtcca-verifier" ] +itrustee-verifier = [] +virtcca-verifier = ["attester/virtcca-attester"] 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 = ::std::mem::MaybeUninit::uninit(); + let ptr = UNINIT.as_ptr(); + assert_eq!( + ::std::mem::size_of::(), + 16usize, + concat!("Size of: ", stringify!(buffer_data)) + ); + assert_eq!( + ::std::mem::align_of::(), + 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..f8038ba --- /dev/null +++ b/service/attestation/attestation-service/verifier/src/itrustee/mod.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. + */ + +//! itrustee verifier plugin + +use super::*; +use log; +use std::path::Path; + +mod itrustee; + +const ITRUSTEE_REF_VALUE_FILE: &str = "/etc/attestation/itrustee/basevalue.txt"; + +#[derive(Debug, Default)] +pub struct ItrusteeVerifier {} + +impl ItrusteeVerifier { + pub async fn evaluate(&self, user_data: &[u8], evidence: &[u8]) -> Result<()> { + return evalute_wrapper(user_data, evidence); + } +} + +fn evalute_wrapper(user_data: &[u8], evidence: &[u8]) -> Result<()> { + 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, + }; + 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 mut ref_file = String::from(ITRUSTEE_REF_VALUE_FILE); + let basevalue = ref_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); + } + } + Ok(()) +} 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..f3c9157 --- /dev/null +++ b/service/attestation/attestation-service/verifier/src/lib.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. + */ + +//! Unified tee verifier +//! +//! This crate provides unified APIs to verify TEE evidence. + +use anyhow::*; +use serde_json; +use async_trait::async_trait; + +use attester::{Evidence, TeeType}; + +#[cfg(feature = "itrustee-verifier")] +mod itrustee; + +#[cfg(feature = "virtcca-verifier")] +mod virtcca; + +#[derive(Debug, Default)] +pub struct Verifier {} + +#[async_trait] +pub trait VerifierAPIs { + async fn verify_evidence(&self, user_data: &[u8], evidence: &[u8]) -> Result<()>; +} + +const MAX_CHALLENGE_LEN: usize = 64; + +#[async_trait] +impl VerifierAPIs for Verifier { + async fn verify_evidence(&self, user_data: &[u8], evidence: &[u8]) -> Result<()> { + let len = user_data.len(); + if len <= 0 || len > MAX_CHALLENGE_LEN { + log::error!("challenge len is error, expecting 0 < len <= {}, got {}", MAX_CHALLENGE_LEN, len); + bail!("challenge len is error, expecting 0 < len <= {}, got {}", MAX_CHALLENGE_LEN, len); + } + let aa_evidence: Evidence = serde_json::from_slice(evidence)?; + let tee_type = aa_evidence.tee; + let evidence = aa_evidence.evidence.as_bytes(); + match tee_type { + #[cfg(feature = "itrustee-verifier")] + TeeType::Itrustee => itrustee::ItrusteeVerifier::default().evaluate(user_data, evidence).await, + #[cfg(feature = "virtcca-verifier")] + TeeType::Virtcca => virtcca::VirtCCAVerifier::default().evaluate(user_data, evidence).await, + _ => bail!("unsupported tee type:{:?}", tee_type), + } + } +} \ No newline at end of file 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..3994743 --- /dev/null +++ b/service/attestation/attestation-service/verifier/src/virtcca/mod.rs @@ -0,0 +1,373 @@ +/* + * 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 anyhow::{Result, bail, anyhow}; +use cose::keys::CoseKey; +use cose::message::CoseMessage; +use ciborium; +use ciborium::value::Value; +use openssl::rsa; +use openssl::pkey::Public; +use openssl::x509; +use openssl::pkey::PKey; +use log; + +use attester::virtcca::VirtccaEvidence; + +const VIRTCCA_ROOT_CERT: &str = "/etc/attestation/virtcca/Huawei Equipment Root CA.pem"; +const VIRTCCA_SUB_CERT: &str = "/etc/attestation/virtcca/Huawei IT Product CA.pem"; +const VIRTCCA_REF_VALUE_FILE: &str = "/etc/attestation/virtcca/ref_value.json"; + +#[derive(Debug, Default)] +pub struct VirtCCAVerifier {} + +impl VirtCCAVerifier { + pub async fn evaluate(&self, user_data: &[u8], evidence: &[u8]) -> Result<()> { + 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, // 44238 => bytes .size {32,48,64} + pub rem: [Vec; CVM_REM_ARR_SIZE], // 44239 => [ 4*4 bytes .size {32,48,64} ] + pub hash_alg: String, // 44236 => text + pub pub_key: [u8; CVM_PUB_KEY_SIZE], // 44237 => bytes .size 550 + pub pub_key_hash_alg: String, // 44240 => text +} + +pub struct Evidence { + /// 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<()> { + 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)?; + + Ok(()) + } + fn verify_platform_token(&mut self, dev_cert: &[u8]) -> Result<()> { + // todo verify platform COSE_Sign1 by dev_cert + + // 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))?; + 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))?; + 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))?; + 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 + + // 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 + + // verify cvm token with reference value + self.compare_with_ref()?; + + Ok(()) + } + + 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 { + let pub_key: rsa::Rsa = 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) -> Result { + 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); + println!("[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) -> Result { + let payload: Vec = 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> { + 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 { + 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(()) + } +} + -- 2.33.0