diff --git a/Cargo.lock b/Cargo.lock index c65fb16cac..11535d30e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5222,6 +5222,7 @@ dependencies = [ "assert-json-diff", "async-trait", "az-snp-vtpm", + "az-tdx-vtpm", "base64 0.21.6", "bincode", "byteorder", diff --git a/Cargo.toml b/Cargo.toml index 2bc3afc89a..90ec327d47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ chrono = "0.4.19" clap = { version = "4", features = ["derive"] } env_logger = "0.10.0" hex = "0.4.3" -kbs-types = "0.5" +kbs-types = "0.5.3" log = "0.4.17" prost = "0.11.0" rstest = "0.18.1" diff --git a/attestation-service/attestation-service/Cargo.toml b/attestation-service/attestation-service/Cargo.toml index d95fa7e9af..d3bda03d33 100644 --- a/attestation-service/attestation-service/Cargo.toml +++ b/attestation-service/attestation-service/Cargo.toml @@ -9,6 +9,7 @@ all-verifier = [ "verifier/all-verifier" ] tdx-verifier = [ "verifier/tdx-verifier" ] sgx-verifier = [ "verifier/sgx-verifier" ] az-snp-vtpm-verifier = [ "verifier/az-snp-vtpm-verifier" ] +az-tdx-vtpm-verifier = [ "verifier/az-tdx-vtpm-verifier" ] snp-verifier = [ "verifier/snp-verifier" ] csv-verifier = [ "verifier/csv-verifier" ] cca-verifier = [ "verifier/cca-verifier" ] @@ -42,7 +43,6 @@ clap = { workspace = true, optional = true } env_logger = { workspace = true, optional = true } futures = "0.3.17" hex.workspace = true -# TODO: change it to "0.5", once released. kbs-types.workspace = true lazy_static = "1.4.0" log.workspace = true diff --git a/attestation-service/verifier/Cargo.toml b/attestation-service/verifier/Cargo.toml index 9f3bd157fd..afb6f8817a 100644 --- a/attestation-service/verifier/Cargo.toml +++ b/attestation-service/verifier/Cargo.toml @@ -5,10 +5,11 @@ edition = "2021" [features] default = [ "all-verifier" ] -all-verifier = [ "tdx-verifier", "sgx-verifier", "snp-verifier", "az-snp-vtpm-verifier", "csv-verifier", "cca-verifier" ] +all-verifier = [ "tdx-verifier", "sgx-verifier", "snp-verifier", "az-snp-vtpm-verifier", "az-tdx-vtpm-verifier", "csv-verifier", "cca-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" ] +az-tdx-vtpm-verifier = [ "az-tdx-vtpm", "openssl", "tdx-verifier" ] snp-verifier = [ "asn1-rs", "openssl", "sev", "x509-parser" ] csv-verifier = [ "openssl", "csv-rs", "codicon" ] cca-verifier = [ "ear", "veraison-apiclient" ] @@ -18,6 +19,7 @@ anyhow.workspace = true asn1-rs = { version = "0.5.1", optional = true } async-trait.workspace = true az-snp-vtpm = { version = "0.4", default-features = false, features = ["verifier"], optional = true } +az-tdx-vtpm = { version = "0.4", default-features = false, features = ["verifier"], optional = true } base64 = "0.21" bincode = "1.3.3" byteorder = "1" diff --git a/attestation-service/verifier/src/az_snp_vtpm/mod.rs b/attestation-service/verifier/src/az_snp_vtpm/mod.rs index 080ce08d98..d91108ba8c 100644 --- a/attestation-service/verifier/src/az_snp_vtpm/mod.rs +++ b/attestation-service/verifier/src/az_snp_vtpm/mod.rs @@ -125,16 +125,16 @@ fn verify_snp_report( mod tests { use super::*; - const REPORT: &[u8; 2048] = include_bytes!("../../test_data/az-hcl-data.bin"); - const SIGNATURE: &[u8; 256] = include_bytes!("../../test_data/az-vtpm-quote-sig.bin"); - const MESSAGE: &[u8; 122] = include_bytes!("../../test_data/az-vtpm-quote-msg.bin"); + const REPORT: &[u8; 2048] = include_bytes!("../../test_data/az-snp-vtpm/hcl-report.bin"); + const SIGNATURE: &[u8; 256] = include_bytes!("../../test_data/az-snp-vtpm/tpm-quote.sig"); + const MESSAGE: &[u8; 122] = include_bytes!("../../test_data/az-snp-vtpm/tpm-quote.msg"); const REPORT_DATA: &[u8] = "challenge".as_bytes(); #[test] fn test_verify_snp_report() { let hcl_report = HclReport::new(REPORT.to_vec()).unwrap(); let snp_report = hcl_report.try_into().unwrap(); - let vcek = Vcek::from_pem(include_str!("../../test_data/az-vcek.pem")).unwrap(); + let vcek = Vcek::from_pem(include_str!("../../test_data/az-snp-vtpm/vcek.pem")).unwrap(); let vendor_certs = load_milan_cert_chain().as_ref().unwrap(); verify_snp_report(&snp_report, &vcek, vendor_certs).unwrap(); } @@ -146,7 +146,7 @@ mod tests { wrong_report[0x00b0] = 0; let hcl_report = HclReport::new(wrong_report.to_vec()).unwrap(); let snp_report = hcl_report.try_into().unwrap(); - let vcek = Vcek::from_pem(include_str!("../../test_data/az-vcek.pem")).unwrap(); + let vcek = Vcek::from_pem(include_str!("../../test_data/az-snp-vtpm/vcek.pem")).unwrap(); let vendor_certs = load_milan_cert_chain().as_ref().unwrap(); verify_snp_report(&snp_report, &vcek, vendor_certs).unwrap_err(); } @@ -197,7 +197,7 @@ mod tests { signature: SIGNATURE.to_vec(), message: MESSAGE.to_vec(), }; - let report = include_bytes!("../../test_data/az-hcl-data.bin"); + let report = include_bytes!("../../test_data/az-snp-vtpm/hcl-report.bin"); let hcl_report = HclReport::new(report.to_vec()).unwrap(); let mut report_data = REPORT_DATA.to_vec(); report_data.reverse(); diff --git a/attestation-service/verifier/src/az_tdx_vtpm/mod.rs b/attestation-service/verifier/src/az_tdx_vtpm/mod.rs new file mode 100644 index 0000000000..fbe8ebaea0 --- /dev/null +++ b/attestation-service/verifier/src/az_tdx_vtpm/mod.rs @@ -0,0 +1,146 @@ +// Copyright (c) Microsoft Corporation. +// +// SPDX-License-Identifier: Apache-2.0 +// + +use super::tdx::claims::generate_parsed_claim; +use super::tdx::quote::{ecdsa_quote_verification, parse_tdx_quote, Quote as TdQuote}; +use super::{TeeEvidenceParsedClaim, Verifier}; +use crate::{regularize_data, InitDataHash, ReportData}; +use anyhow::{bail, Context, Result}; +use async_trait::async_trait; +use az_tdx_vtpm::hcl::HclReport; +use az_tdx_vtpm::vtpm::Quote as TpmQuote; +use log::{debug, warn}; +use openssl::pkey::PKey; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +struct Evidence { + tpm_quote: TpmQuote, + hcl_report: Vec, + td_quote: Vec, +} + +#[derive(Default)] +pub struct AzTdxVtpm; + +#[async_trait] +impl Verifier for AzTdxVtpm { + /// The following verification steps are performed: + /// 1. TPM Quote has been signed by AK included in the HCL variable data + /// 2. Attestation nonce matches TPM Quote nonce + /// 3. TD Quote is genuine + /// 4. TD Report's report_data field matches hashed HCL variable data + async fn evaluate( + &self, + evidence: &[u8], + expected_report_data: &ReportData, + expected_init_data_hash: &InitDataHash, + ) -> Result { + let ReportData::Value(expected_report_data) = expected_report_data else { + bail!("unexpected empty report data"); + }; + let expected_report_data = + regularize_data(expected_report_data, 64, "REPORT_DATA", "Azure TDX vTPM"); + + if let InitDataHash::Value(_) = expected_init_data_hash { + warn!("Azure TDX vTPM verifier does not support verify init data hash, will ignore the input `init_data_hash`"); + } + + let evidence = serde_json::from_slice::(evidence) + .context("Failed to deserialize Azure vTPM TDX evidence")?; + + let hcl_report = HclReport::new(evidence.hcl_report)?; + verify_tpm_quote(&evidence.tpm_quote, &hcl_report, &expected_report_data)?; + + ecdsa_quote_verification(&evidence.td_quote).await?; + let td_quote = parse_tdx_quote(&evidence.td_quote)?; + + verify_report_data(&hcl_report, &td_quote)?; + + let claim = generate_parsed_claim(td_quote, None)?; + Ok(claim) + } +} + +fn verify_report_data(hcl_report: &HclReport, td_quote: &TdQuote) -> Result<()> { + let var_data_hash = hcl_report.var_data_sha256(); + if var_data_hash != td_quote.report_body.report_data[..32] { + bail!("TDX Quote report data mismatch"); + } + debug!("Report data verification completed successfully."); + Ok(()) +} + +fn verify_tpm_quote(quote: &TpmQuote, hcl_report: &HclReport, report_data: &[u8]) -> Result<()> { + let ak_pub = hcl_report.ak_pub().context("Failed to get AKpub")?; + let der = ak_pub.key.try_to_der()?; + let ak_pub = PKey::public_key_from_der(&der).context("Failed to parse AKpub")?; + + quote + .verify(&ak_pub, report_data) + .context("Failed to verify vTPM quote")?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + const REPORT: &[u8; 2600] = include_bytes!("../../test_data/az-tdx-vtpm/hcl-report.bin"); + const SIGNATURE: &[u8; 256] = include_bytes!("../../test_data/az-tdx-vtpm/tpm-quote.sig"); + const MESSAGE: &[u8; 126] = include_bytes!("../../test_data/az-tdx-vtpm/tpm-quote.msg"); + const TD_QUOTE: &[u8; 5006] = include_bytes!("../../test_data/az-tdx-vtpm/td-quote.bin"); + + #[test] + fn test_verify_report_data() { + let hcl_report = HclReport::new(REPORT.to_vec()).unwrap(); + let td_quote = parse_tdx_quote(TD_QUOTE).unwrap(); + verify_report_data(&hcl_report, &td_quote).unwrap(); + } + + #[test] + fn test_verify_report_data_failure() { + let mut wrong_report = REPORT.clone(); + wrong_report[0x0880] += 1; + let hcl_report = HclReport::new(wrong_report.to_vec()).unwrap(); + let td_quote = parse_tdx_quote(TD_QUOTE).unwrap(); + verify_report_data(&hcl_report, &td_quote).unwrap_err(); + } + + #[test] + fn test_verify_quote() { + let quote = TpmQuote { + signature: SIGNATURE.to_vec(), + message: MESSAGE.to_vec(), + }; + let hcl_report = HclReport::new(REPORT.to_vec()).unwrap(); + let nonce = "tdx challenge".as_bytes(); + verify_tpm_quote("e, &hcl_report, nonce).unwrap(); + } + + #[test] + fn test_verify_quote_signature_failure() { + let mut wrong_message = MESSAGE.clone(); + wrong_message.reverse(); + let wrong_quote = TpmQuote { + signature: SIGNATURE.to_vec(), + message: wrong_message.to_vec(), + }; + let hcl_report = HclReport::new(REPORT.to_vec()).unwrap(); + let nonce = "tdx challenge".as_bytes(); + verify_tpm_quote(&wrong_quote, &hcl_report, nonce).unwrap_err(); + } + + #[test] + fn test_verify_quote_nonce_failure() { + let quote = TpmQuote { + signature: SIGNATURE.to_vec(), + message: MESSAGE.to_vec(), + }; + let hcl_report = HclReport::new(REPORT.to_vec()).unwrap(); + let nonce = "wrong".as_bytes(); + verify_tpm_quote("e, &hcl_report, nonce).unwrap_err(); + } +} diff --git a/attestation-service/verifier/src/lib.rs b/attestation-service/verifier/src/lib.rs index 67658734ee..464aba8fbf 100644 --- a/attestation-service/verifier/src/lib.rs +++ b/attestation-service/verifier/src/lib.rs @@ -10,6 +10,9 @@ pub mod sample; #[cfg(feature = "az-snp-vtpm-verifier")] pub mod az_snp_vtpm; +#[cfg(feature = "az-tdx-vtpm-verifier")] +pub mod az_tdx_vtpm; + #[cfg(feature = "snp-verifier")] pub mod snp; @@ -38,6 +41,15 @@ pub fn to_verifier(tee: &Tee) -> Result> { } } } + Tee::AzTdxVtpm => { + cfg_if::cfg_if! { + if #[cfg(feature = "az-tdx-vtpm-verifier")] { + Ok(Box::::default() as Box) + } else { + bail!("feature `az-tdx-vtpm-verifier` is not enabled for `verifier` crate."); + } + } + } Tee::Tdx => { cfg_if::cfg_if! { if #[cfg(feature = "tdx-verifier")] { @@ -87,7 +99,6 @@ pub fn to_verifier(tee: &Tee) -> Result> { } } } - Tee::AzTdxVtpm => todo!(), } } diff --git a/attestation-service/verifier/src/tdx/mod.rs b/attestation-service/verifier/src/tdx/mod.rs index 2b73e23a51..3b8c7dd070 100644 --- a/attestation-service/verifier/src/tdx/mod.rs +++ b/attestation-service/verifier/src/tdx/mod.rs @@ -10,9 +10,9 @@ use eventlog::{CcEventLog, Rtmr}; use quote::{ecdsa_quote_verification, parse_tdx_quote}; use serde::{Deserialize, Serialize}; -mod claims; +pub(crate) mod claims; mod eventlog; -mod quote; +pub(crate) mod quote; #[derive(Serialize, Deserialize, Debug)] struct TdxEvidence { diff --git a/attestation-service/verifier/test_data/az-hcl-data.bin b/attestation-service/verifier/test_data/az-snp-vtpm/hcl-report.bin similarity index 100% rename from attestation-service/verifier/test_data/az-hcl-data.bin rename to attestation-service/verifier/test_data/az-snp-vtpm/hcl-report.bin diff --git a/attestation-service/verifier/test_data/az-vtpm-quote-msg.bin b/attestation-service/verifier/test_data/az-snp-vtpm/tpm-quote.msg similarity index 100% rename from attestation-service/verifier/test_data/az-vtpm-quote-msg.bin rename to attestation-service/verifier/test_data/az-snp-vtpm/tpm-quote.msg diff --git a/attestation-service/verifier/test_data/az-vtpm-quote-sig.bin b/attestation-service/verifier/test_data/az-snp-vtpm/tpm-quote.sig similarity index 100% rename from attestation-service/verifier/test_data/az-vtpm-quote-sig.bin rename to attestation-service/verifier/test_data/az-snp-vtpm/tpm-quote.sig diff --git a/attestation-service/verifier/test_data/az-vcek.pem b/attestation-service/verifier/test_data/az-snp-vtpm/vcek.pem similarity index 100% rename from attestation-service/verifier/test_data/az-vcek.pem rename to attestation-service/verifier/test_data/az-snp-vtpm/vcek.pem diff --git a/attestation-service/verifier/test_data/az-tdx-vtpm/hcl-report.bin b/attestation-service/verifier/test_data/az-tdx-vtpm/hcl-report.bin new file mode 100644 index 0000000000..cca12d37ac Binary files /dev/null and b/attestation-service/verifier/test_data/az-tdx-vtpm/hcl-report.bin differ diff --git a/attestation-service/verifier/test_data/az-tdx-vtpm/td-quote.bin b/attestation-service/verifier/test_data/az-tdx-vtpm/td-quote.bin new file mode 100644 index 0000000000..2fb8f51ece Binary files /dev/null and b/attestation-service/verifier/test_data/az-tdx-vtpm/td-quote.bin differ diff --git a/attestation-service/verifier/test_data/az-tdx-vtpm/tpm-quote.msg b/attestation-service/verifier/test_data/az-tdx-vtpm/tpm-quote.msg new file mode 100644 index 0000000000..6653c5d5dd Binary files /dev/null and b/attestation-service/verifier/test_data/az-tdx-vtpm/tpm-quote.msg differ diff --git a/attestation-service/verifier/test_data/az-tdx-vtpm/tpm-quote.sig b/attestation-service/verifier/test_data/az-tdx-vtpm/tpm-quote.sig new file mode 100644 index 0000000000..7f137dd0bb Binary files /dev/null and b/attestation-service/verifier/test_data/az-tdx-vtpm/tpm-quote.sig differ diff --git a/kbs/README.md b/kbs/README.md index dbfa7653a8..ed8ab5a6db 100644 --- a/kbs/README.md +++ b/kbs/README.md @@ -10,7 +10,8 @@ This project relies on the [Attestation-Service (AS)](https://github.com/confide The following TEE platforms are currently supported: - AMD SEV-SNP -- Azure SNP vTPM +- Azure SEV-SNP vTPM +- Azure TDX vTPM - Intel SGX - Intel TDX diff --git a/kbs/src/api/src/attestation/coco/grpc.rs b/kbs/src/api/src/attestation/coco/grpc.rs index d37b215e7e..0121b057ab 100644 --- a/kbs/src/api/src/attestation/coco/grpc.rs +++ b/kbs/src/api/src/attestation/coco/grpc.rs @@ -31,6 +31,7 @@ pub const COCO_AS_HASH_ALGORITHM: &str = "sha384"; fn to_grpc_tee(tee: Tee) -> GrpcTee { match tee { Tee::AzSnpVtpm => GrpcTee::AzSnpVtpm, + Tee::AzTdxVtpm => GrpcTee::AzTdxVtpm, Tee::Cca => GrpcTee::Cca, Tee::Csv => GrpcTee::Csv, Tee::Sample => GrpcTee::Sample, diff --git a/kbs/test/Makefile b/kbs/test/Makefile index c75dd9d694..0a72ae80df 100644 --- a/kbs/test/Makefile +++ b/kbs/test/Makefile @@ -25,6 +25,7 @@ install-dependencies: sudo apt-get install -y \ build-essential \ clang \ + libsgx-dcap-default-qpl \ libsgx-dcap-quote-verify-dev \ libtdx-attest-dev \ libtss2-dev \