diff --git a/as-types/Cargo.toml b/as-types/Cargo.toml index a4fdc91..fcdf2f6 100644 --- a/as-types/Cargo.toml +++ b/as-types/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -kbs-types = "0.4" +# TODO: change it to "0.5", once released. +kbs-types = { git = "https://github.com/virtee/kbs-types", rev = "c90df0e" } serde.workspace = true serde_json.workspace = true diff --git a/attestation-service/Cargo.toml b/attestation-service/Cargo.toml index ed68803..7b52d28 100644 --- a/attestation-service/Cargo.toml +++ b/attestation-service/Cargo.toml @@ -5,11 +5,12 @@ edition = "2021" [features] default = [ "rvps-native", "all-verifier" ] -all-verifier = [ "tdx-verifier", "sgx-verifier", "snp-verifier", "az-snp-vtpm-verifier" ] +all-verifier = [ "tdx-verifier", "sgx-verifier", "snp-verifier", "az-snp-vtpm-verifier", "csv-verifier" ] tdx-verifier = [ "eventlog-rs", "scroll", "sgx-dcap-quoteverify-rs" ] sgx-verifier = [ "scroll", "sgx-dcap-quoteverify-rs" ] az-snp-vtpm-verifier = [ "az-snp-vtpm", "sev" ] snp-verifier = [ "asn1-rs", "openssl", "sev", "x509-parser" ] +csv-verifier = [ "openssl", "csv-rs", "codicon" ] rvps-native = [] rvps-grpc = [ "tonic" ] @@ -29,10 +30,11 @@ eventlog-rs = { version = "0.1.3", optional = true } futures = "0.3.17" hex = "0.4.3" sgx-dcap-quoteverify-rs = { git = "https://github.com/intel/SGXDataCenterAttestationPrimitives", tag = "DCAP_1.16", optional = true } -kbs-types = "0.4" +# TODO: change it to "0.5", once released. +kbs-types = { git = "https://github.com/virtee/kbs-types", rev = "c90df0e" } lazy_static = "1.4.0" log.workspace = true -openssl = { version = "0.10.43", optional = true } +openssl = { version = "0.10.55", optional = true } path-clean = "1.0.1" prost.workspace = true scroll = { version = "0.11.0", default-features = false, features = ["derive"], optional = true } @@ -50,6 +52,9 @@ tokio.workspace = true tonic = { workspace = true, optional = true } uuid = { version = "1.1.2", features = ["v4"] } x509-parser = { version = "0.14.0", optional = true } +# TODO: change it to "0.1", once released. +csv-rs = { git = "https://gitee.com/anolis/csv-rs", rev = "8a90aac", optional = true } +codicon = { version = "3.0", optional = true } [build-dependencies] shadow-rs.workspace = true diff --git a/attestation-service/src/verifier/csv/hrk.cert b/attestation-service/src/verifier/csv/hrk.cert new file mode 100644 index 0000000..28b91bb Binary files /dev/null and b/attestation-service/src/verifier/csv/hrk.cert differ diff --git a/attestation-service/src/verifier/csv/mod.rs b/attestation-service/src/verifier/csv/mod.rs new file mode 100644 index 0000000..18def9a --- /dev/null +++ b/attestation-service/src/verifier/csv/mod.rs @@ -0,0 +1,164 @@ +// Copyright (C) Hygon Info Technologies Ltd. +// +// SPDX-License-Identifier: Apache-2.0 +// + +use anyhow::{anyhow, Context, Result}; +extern crate serde; +use self::serde::{Deserialize, Serialize}; +use super::*; +use async_trait::async_trait; +use base64::Engine; +use serde_json::json; +use sha2::{Digest, Sha384}; +use csv_rs::{ + api::guest::{AttestationReport, Body}, + certs::{ca, csv, Verifiable}, +}; +use codicon::Decoder; +use kbs_types::TeePubKey; + +#[derive(Serialize, Deserialize)] +struct CertificateChain { + hsk: ca::Certificate, + cek: csv::Certificate, + pek: csv::Certificate, +} + +#[derive(Serialize, Deserialize)] +struct CsvEvidence { + attestation_report: AttestationReport, + cert_chain: CertificateChain, +} + +pub const HRK: &[u8] = include_bytes!("hrk.cert"); + +#[derive(Debug, Default)] +pub struct CsvVerifier {} + +#[async_trait] +impl Verifier for CsvVerifier { + async fn evaluate( + &self, + nonce: String, + attestation: &Attestation, + ) -> Result { + let tee_evidence = serde_json::from_str::(&attestation.tee_evidence) + .context("Deserialize Quote failed.")?; + + verify_report_signature(&tee_evidence)?; + + let report_raw = restore_attestation_report(tee_evidence.attestation_report)?; + + let expected_report_data = calculate_expected_report_data(&nonce, &attestation.tee_pubkey); + if report_raw.body.report_data != expected_report_data { + return Err(anyhow!("Report Data Mismatch")); + } + + parse_tee_evidence(&report_raw) + } +} + +fn calculate_expected_report_data(nonce: &String, tee_pubkey: &TeePubKey) -> [u8; 64] { + let mut hasher = Sha384::new(); + + hasher.update(nonce.as_bytes()); + hasher.update(&tee_pubkey.k_mod); + hasher.update(&tee_pubkey.k_exp); + + let partial_hash = hasher.finalize(); + + let mut hash = [0u8; 64]; + hash[..48].copy_from_slice(&partial_hash); + + hash +} + +fn verify_report_signature(evidence: &CsvEvidence) -> Result<()> { + // Verify certificate chain + let hrk = ca::Certificate::decode(&mut &HRK[..], ())?; + (&hrk, &hrk).verify() + .context("HRK cert Signature validation failed.")?; + (&hrk, &evidence.cert_chain.hsk).verify() + .context("HSK cert Signature validation failed.")?; + (&evidence.cert_chain.hsk, &evidence.cert_chain.cek).verify() + .context("CEK cert Signature validation failed.")?; + (&evidence.cert_chain.cek, &evidence.cert_chain.pek).verify() + .context("PEK cert Signature validation failed.")?; + + // Verify the TEE Hardware signature. + (&evidence.cert_chain.pek, &evidence.attestation_report).verify() + .context("Attestation Report Signature validation failed.")?; + + Ok(()) +} + +fn xor_with_anonce(data: &mut [u8], anonce: &u32){ + let mut anonce_array = [0u8; 4]; + anonce_array[..].copy_from_slice(&anonce.to_le_bytes()); + + for (index, item) in data.iter_mut().enumerate() { + *item ^= anonce_array[index % 4]; + } +} + +fn restore_attestation_report(report: AttestationReport) -> Result { + let body = &report.body; + let mut user_pubkey_digest = body.user_pubkey_digest.clone(); + xor_with_anonce(&mut user_pubkey_digest, &report.anonce); + let mut vm_id = body.vm_id.clone(); + xor_with_anonce(&mut vm_id, &report.anonce); + let mut vm_version = body.vm_version.clone(); + xor_with_anonce(&mut vm_version, &report.anonce); + let mut report_data = body.report_data.clone(); + xor_with_anonce(&mut report_data, &report.anonce); + let mut mnonce = body.mnonce.clone(); + xor_with_anonce(&mut mnonce, &report.anonce); + let mut measure = body.measure.clone(); + xor_with_anonce(&mut measure, &report.anonce); + + let policy = report.body.policy.xor(&report.anonce); + + Ok(AttestationReport { + body: Body { + user_pubkey_digest, + vm_id, + vm_version, + report_data, + mnonce, + measure, + policy, + }, + ..report + }) +} + +// Dump the CSV information from the report. +fn parse_tee_evidence(report: &AttestationReport) -> Result { + let body = &report.body; + let claims_map = json!({ + // policy fields + "policy_nodbg": format!("{}",body.policy.nodbg()), + "policy_noks": format!("{}", body.policy.noks()), + "policy_es": format!("{}", body.policy.es()), + "policy_nosend": format!("{}", body.policy.nosend()), + "policy_domain": format!("{}", body.policy.domain()), + "policy_csv": format!("{}", body.policy.csv()), + "policy_csv3": format!("{}", body.policy.csv3()), + "policy_asid_reuse": format!("{}", body.policy.asid_reuse()), + "policy_hsk_version": format!("{}", body.policy.hsk_version()), + "policy_cek_version": format!("{}", body.policy.cek_version()), + "policy_api_major": format!("{}", body.policy.api_major()), + "policy_api_minor": format!("{}", body.policy.api_minor()), + + // launch info inject with pdh and session data + "user_pubkey_digest": format!("{}", base64::engine::general_purpose::STANDARD.encode(body.user_pubkey_digest)), + "vm_id": format!("{}", base64::engine::general_purpose::STANDARD.encode(body.vm_id)), + "vm_version": format!("{}", base64::engine::general_purpose::STANDARD.encode(body.vm_version)), + + // measurement + "measurement": format!("{}", base64::engine::general_purpose::STANDARD.encode(body.measure)), + }); + + Ok(claims_map as TeeEvidenceParsedClaim) +} diff --git a/attestation-service/src/verifier/mod.rs b/attestation-service/src/verifier/mod.rs index 7a9950f..0285958 100644 --- a/attestation-service/src/verifier/mod.rs +++ b/attestation-service/src/verifier/mod.rs @@ -17,6 +17,9 @@ pub mod tdx; #[cfg(feature = "sgx-verifier")] pub mod sgx; +#[cfg(feature = "csv-verifier")] +pub mod csv; + pub(crate) fn to_verifier(tee: &Tee) -> Result> { match tee { Tee::Sev | Tee::Cca => todo!(), @@ -57,6 +60,15 @@ pub(crate) fn to_verifier(tee: &Tee) -> Result> } } } + Tee::Csv => { + cfg_if::cfg_if! { + if #[cfg(feature = "csv-verifier")] { + Ok(Box::::default() as Box) + } else { + anyhow::bail!("feature `csv-verifier` is not enabled!"); + } + } + } } } diff --git a/bin/grpc-as/src/server.rs b/bin/grpc-as/src/server.rs index b33c411..b87e0e9 100644 --- a/bin/grpc-as/src/server.rs +++ b/bin/grpc-as/src/server.rs @@ -29,6 +29,7 @@ fn to_kbs_tee(tee: GrpcTee) -> Tee { GrpcTee::Sgx => Tee::Sgx, GrpcTee::Snp => Tee::Snp, GrpcTee::Tdx => Tee::Tdx, + GrpcTee::Csv => Tee::Csv, GrpcTee::Sample => Tee::Sample, } } diff --git a/protos/attestation.proto b/protos/attestation.proto index f9def45..3746e3c 100644 --- a/protos/attestation.proto +++ b/protos/attestation.proto @@ -8,6 +8,7 @@ enum Tee { SNP = 2; TDX = 3; Sample = 4; + CSV = 6; } message AttestationRequest {