diff --git a/Cargo.toml b/Cargo.toml index c22aabc..8fda620 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,3 +32,4 @@ chrono = "0.4.40" tokio = { version = "1.44.1", features = ["rt-multi-thread"] } x509-parser = "0.15.1" clap = { version = "4.0", features = ["derive", "env"] } +pem = "3.0.5" diff --git a/tdx/Cargo.toml b/tdx/Cargo.toml index bed998e..83cf922 100644 --- a/tdx/Cargo.toml +++ b/tdx/Cargo.toml @@ -21,7 +21,6 @@ cbindgen = "0.29.0" lto = true [dependencies] -dcap-rs.workspace = true rand.workspace = true ureq.workspace = true base64-url.workspace = true @@ -33,6 +32,8 @@ chrono.workspace = true tokio.workspace = true x509-parser.workspace = true clap.workspace = true +pem.workspace = true once_cell = { version = "1.20.2", optional=true } +dcap-rs = { git = "https://github.com/automata-network/dcap-rs", rev="ee2b89d" } coco-provider = { git = "https://github.com/automata-network/coco-provider-sdk", optional = true, default-features = false } diff --git a/tdx/README.md b/tdx/README.md index adc04b7..e2110fc 100644 --- a/tdx/README.md +++ b/tdx/README.md @@ -72,7 +72,7 @@ let tdx = Tdx::new(); ### Generate Attestation To generate an attestation with default options, you can do so like this: ```rust -let (report, _) = tdx.get_attestation_report()?; +let (report, _) = tdx.get_attestation_report_raw()?; ``` If you wish to customise options for the attestation report, you can do something like this: @@ -135,4 +135,4 @@ Please follow Intel official DCAP repo [SGXDataCenterAttestationPrimitives](http cargo build --example inspect sudo ./target/debug/examples/inspect --report tdx/examples/testdata/tdx_v4_quote.bin sudo ./target/debug/examples/inspect --report tdx/examples/testdata/sgx_v3_quote.bin - ``` \ No newline at end of file + ``` diff --git a/tdx/examples/attestation.rs b/tdx/examples/attestation.rs index a8afe74..00d1db3 100644 --- a/tdx/examples/attestation.rs +++ b/tdx/examples/attestation.rs @@ -1,3 +1,4 @@ +use dcap_rs::types::quote::Quote; use tdx::Tdx; fn main() { @@ -5,12 +6,19 @@ fn main() { let tdx = Tdx::new(); // Retrieve an attestation report with default options passed to the hardware device - let (report, _) = tdx.get_attestation_report().unwrap(); + // ================================================================================ + let (report_raw, _) = tdx.get_attestation_report_raw().unwrap(); + let report = Quote::read(&mut report_raw.as_slice()).unwrap(); println!("Attestation Report: {:?}", report); // Verify the attestation report - tdx.verify_attestation_report(&report).unwrap(); + // Use either function, they both do the same thing, except one takes in bytes + // while the other takes in a Quote struct. + // ================================================================================ + // tdx.verify_attestation_report(report).unwrap(); + tdx.verify_attestation_report_raw(&mut report_raw.as_slice()) + .unwrap(); println!("Verification successful!"); } diff --git a/tdx/examples/fmspc.rs b/tdx/examples/fmspc.rs index d640e18..28cb07c 100644 --- a/tdx/examples/fmspc.rs +++ b/tdx/examples/fmspc.rs @@ -1,15 +1,16 @@ -use tdx::Tdx; +use dcap_rs::types::quote::Quote; use tdx::utils::get_pck_fmspc_and_issuer; +use tdx::Tdx; fn main() { // Initialise a TDX object let tdx = Tdx::new(); // Retrieve an attestation report with default options passed to the hardware device - let (report, _) = tdx.get_attestation_report().unwrap(); - // println!("Attestation Report: {:?}", report); + let (report_raw, _) = tdx.get_attestation_report_raw().unwrap(); + let report = Quote::read(&mut report_raw.as_slice()).unwrap(); - let (fmspc, _) = get_pck_fmspc_and_issuer(&report); + let (fmspc, _) = get_pck_fmspc_and_issuer(&report).unwrap(); println!("FMSPC: {:?}", fmspc.to_uppercase()); if report.header.tee_type == 0 { println!("Platform: SGX"); @@ -17,4 +18,4 @@ fn main() { println!("Platform: TDX"); } println!("Version: {}", report.header.version); -} \ No newline at end of file +} diff --git a/tdx/examples/inspect.rs b/tdx/examples/inspect.rs index 9ef3c4d..232955c 100644 --- a/tdx/examples/inspect.rs +++ b/tdx/examples/inspect.rs @@ -1,9 +1,8 @@ +use dcap_rs::types::quote::Quote; use std::path::PathBuf; use clap::Parser; -use dcap_rs::types::quotes::{QuoteHeader, version_3::QuoteV3, version_4::QuoteV4}; -use dcap_rs::utils::cert::{parse_certchain, parse_pem}; -use tdx::utils::{extract_fmspc_from_extension, get_pck_fmspc_and_issuer}; +use tdx::utils::get_pck_fmspc_and_issuer; #[derive(Parser)] struct Opt { @@ -15,34 +14,17 @@ fn main() -> anyhow::Result<()> { let opt = Opt::parse(); let report_path = opt.report; let report = std::fs::read(&report_path)?; - let header = QuoteHeader::from_bytes(&report[0..48]); - if header.version == 3 { - let quote_v3 = QuoteV3::from_bytes(&report); - let fmspc = get_pck_fmspc_from_v3_quote("e_v3); - println!("FMSPC: {:?}", fmspc.to_uppercase()); + let report = Quote::read(&mut report.as_slice())?; + let report_version = u32::from(report.header.version); + + let (fmspc, _) = get_pck_fmspc_and_issuer(&report).unwrap(); + println!("FMSPC: {:?}", fmspc.to_uppercase()); + if report.header.tee_type == 0 { println!("Platform: SGX"); - println!("Version: V3"); - } else if header.version == 4 { - let quote_v4 = QuoteV4::from_bytes(&report); - let (fmspc, _) = get_pck_fmspc_and_issuer("e_v4); - println!("FMSPC: {:?}", fmspc.to_uppercase()); - if quote_v4.header.tee_type == 0 { - println!("Platform: SGX"); - } else { - println!("Platform: TDX"); - } - println!("Version: V4"); } else { - eprintln!("Unsupported quote version: {}", header.version); + println!("Platform: TDX"); } - Ok(()) -} + println!("Report Version: V{}", report_version); -fn get_pck_fmspc_from_v3_quote(quote: &QuoteV3) -> String { - let pem = parse_pem("e.signature.qe_cert_data.cert_data).expect("Failed to parse cert data"); - let cert_chain = parse_certchain(&pem); - let pck = &cert_chain[0]; - let fmspc_slice = extract_fmspc_from_extension(pck); - let fmspc = hex::encode(fmspc_slice); - fmspc + Ok(()) } diff --git a/tdx/examples/testdata/tdx_v5_quote.bin b/tdx/examples/testdata/tdx_v5_quote.bin new file mode 100644 index 0000000..10758d5 Binary files /dev/null and b/tdx/examples/testdata/tdx_v5_quote.bin differ diff --git a/tdx/src/device.rs b/tdx/src/device.rs index f7f1232..5e617cf 100644 --- a/tdx/src/device.rs +++ b/tdx/src/device.rs @@ -4,7 +4,7 @@ use coco_provider::{ coco::{CocoDeviceType, ReportRequest}, get_coco_provider, CocoProvider, }; -use dcap_rs::types::quotes::version_4::QuoteV4; + use serde::Deserialize; const IMDS_QUOTE_URL: &str = "http://169.254.169.254/acc/tdquote"; @@ -44,11 +44,6 @@ impl Device { Ok(Device { options, provider }) } - pub fn get_attestation_report(&self) -> Result<(QuoteV4, Option>)> { - let (raw_report, var_data) = self.get_attestation_report_raw()?; - Ok((QuoteV4::from_bytes(&raw_report), var_data)) - } - pub fn get_attestation_report_raw(&self) -> Result<(Vec, Option>)> { let report_data = match self.provider.device_type { CocoDeviceType::Tpm => { diff --git a/tdx/src/error.rs b/tdx/src/error.rs index 15088b4..323d83b 100644 --- a/tdx/src/error.rs +++ b/tdx/src/error.rs @@ -74,3 +74,9 @@ impl From for TdxError { TdxError::Anyhow(err.to_string()) } } + +impl From for TdxError { + fn from(err: std::str::Utf8Error) -> Self { + TdxError::IO(format!("{:?}", err)) + } +} diff --git a/tdx/src/lib.rs b/tdx/src/lib.rs index c5fb9fb..38421bc 100644 --- a/tdx/src/lib.rs +++ b/tdx/src/lib.rs @@ -3,17 +3,19 @@ pub mod error; pub mod pccs; pub mod utils; -use dcap_rs::types::collaterals::IntelCollateral; -use dcap_rs::types::quotes::version_4::QuoteV4; -use dcap_rs::utils::quotes::version_4::verify_quote_dcapv4; +use dcap_rs::types::collateral::Collateral; +use dcap_rs::types::quote::Quote; +use dcap_rs::verify_dcap_quote; use error::{Result, TdxError}; use pccs::enclave_id::get_enclave_identity; use pccs::fmspc_tcb::get_tcb_info; use pccs::pcs::{get_certificate_by_id, IPCSDao::CA}; -use std::panic; +use std::time::SystemTime; use tokio::runtime::Runtime; use utils::get_pck_fmspc_and_issuer; +use crate::utils::der_to_pem_bytes; + pub struct Tdx; impl Tdx { @@ -21,30 +23,6 @@ impl Tdx { Tdx } - /// Retrieve an Attestation Report. - /// - /// Returns: - /// - A tuple containing the attestation report and the optional var data. - /// - The attestation report is a `QuoteV4` struct. - /// - The var data is an optional `Vec` containing the var data. - /// Var data is only available if the device resides on an Azure Confidential VM. - /// Var data provided by Azure can be used to verify the contents of the attestation report's report_data - pub fn get_attestation_report(&self) -> Result<(QuoteV4, Option>)> { - let device = device::Device::default()?; - device.get_attestation_report() - } - - /// Retrieve an Attestation Report with options. - /// When available, users can pass in a 64 byte report data when requesting an attestation report. - /// This cannot be used on Azure Confidential VM. - pub fn get_attestation_report_with_options( - &self, - options: device::DeviceOptions, - ) -> Result<(QuoteV4, Option>)> { - let device = device::Device::new(options)?; - device.get_attestation_report() - } - /// Retrieve an Attestation Report in raw bytes. /// /// Returns: @@ -69,8 +47,14 @@ impl Tdx { device.get_attestation_report_raw() } + pub fn verify_attestation_report_raw(&self, report: &mut &[u8]) -> Result<()> { + let quote = Quote::read(report)?; + self.verify_attestation_report(quote)?; + Ok(()) + } + /// This function verifies the chain of trust for the attestation report. - pub fn verify_attestation_report(&self, report: &QuoteV4) -> Result<()> { + pub fn verify_attestation_report(&self, report: Quote) -> Result<()> { // First retrieve all the required collaterals. let rt = Runtime::new().unwrap(); let (root_ca, root_ca_crl) = rt.block_on(get_certificate_by_id(CA::ROOT))?; @@ -78,49 +62,41 @@ impl Tdx { return Err(TdxError::Http("Root CA or CRL is empty".to_string())); } - let (fmspc, pck_type) = get_pck_fmspc_and_issuer(report); + let (fmspc, pck_type) = get_pck_fmspc_and_issuer(&report)?; // tcb_type: 0: SGX, 1: TDX // version: TDX uses TcbInfoV3 let tcb_info = rt.block_on(get_tcb_info(1, &fmspc, 3))?; let quote_version = report.header.version; - let qe_identity = rt.block_on(get_enclave_identity(quote_version as u32))?; + let qe_identity = rt.block_on(get_enclave_identity(quote_version.get() as u32))?; let (signing_ca, _) = rt.block_on(get_certificate_by_id(CA::SIGNING))?; if signing_ca.is_empty() { - return Err(TdxError::Http("Signing CA is empty".to_string())); + return Err(TdxError::Http("TCB Signing CA is empty".to_string())); } + let signing_ca_pem = der_to_pem_bytes(&signing_ca); + let root_ca_pem = der_to_pem_bytes(&root_ca); + let combined_pem = [signing_ca_pem, root_ca_pem].concat(); let (_, pck_crl) = rt.block_on(get_certificate_by_id(pck_type))?; if pck_crl.is_empty() { return Err(TdxError::Http("PCK CRL is empty".to_string())); } - // Pass all the collaterals into a struct for verifying the quote. - let current_time = chrono::Utc::now().timestamp() as u64; - let mut collaterals = IntelCollateral::new(); + let tcb_info_json = std::str::from_utf8(&tcb_info)?; + let qe_identity_json = std::str::from_utf8(&qe_identity)?; - collaterals.set_tcbinfo_bytes(&tcb_info); - collaterals.set_qeidentity_bytes(&qe_identity); - collaterals.set_intel_root_ca_der(&root_ca); - collaterals.set_sgx_tcb_signing_der(&signing_ca); - collaterals.set_sgx_intel_root_ca_crl_der(&root_ca_crl); - match pck_type { - CA::PLATFORM => { - collaterals.set_sgx_platform_crl_der(&pck_crl); - } - CA::PROCESSOR => { - collaterals.set_sgx_processor_crl_der(&pck_crl); - } - _ => { - return Err(TdxError::Http("Unknown PCK Type".to_string())); - } - } - - match panic::catch_unwind(|| verify_quote_dcapv4(report, &collaterals, current_time)) { - Ok(_) => Ok(()), - Err(e) => Err(TdxError::Dcap(format!("DCAP Error: {:?}", e))), - } + // Pass all the collaterals into a struct for verifying the quote. + let collaterals = Collateral::new( + &root_ca_crl, + &pck_crl, + &combined_pem, + &tcb_info_json, + &qe_identity_json, + )?; + + verify_dcap_quote(SystemTime::now(), collaterals, report)?; + Ok(()) } } diff --git a/tdx/src/utils.rs b/tdx/src/utils.rs index 19ec346..1a9bcab 100644 --- a/tdx/src/utils.rs +++ b/tdx/src/utils.rs @@ -1,11 +1,9 @@ +use crate::error::{Result, TdxError}; use crate::CA; -use dcap_rs::types::quotes::version_4::QuoteV4; -use dcap_rs::types::quotes::QeReportCertData; -use dcap_rs::utils::cert::{get_x509_issuer_cn, parse_certchain, parse_pem}; +use dcap_rs::{types::quote::Quote, utils::cert_chain_processor}; use rand::RngCore; use x509_parser::oid_registry::asn1_rs::{oid, FromDer, OctetString, Oid, Sequence}; -use x509_parser::prelude::*; - +use x509_parser::prelude::{parse_x509_pem, X509Certificate}; /// Generates 64 bytes of random data /// Always guaranted to return something (ie, unwrap() can be safely called) pub fn generate_random_data() -> Option<[u8; 64]> { @@ -14,18 +12,30 @@ pub fn generate_random_data() -> Option<[u8; 64]> { Some(data) } -pub fn get_pck_fmspc_and_issuer(quote: &QuoteV4) -> (String, CA) { - let raw_cert_data = QeReportCertData::from_bytes("e.signature.qe_cert_data.cert_data); +pub fn der_to_pem_bytes(der_bytes: &[u8]) -> Vec { + let pem_struct = pem::Pem::new("CERTIFICATE".to_string(), der_bytes.to_vec()); + pem::encode(&pem_struct).into_bytes() +} + +pub fn get_pck_fmspc_and_issuer(quote: &Quote) -> Result<(String, CA)> { + let raw_cert_data = "e.signature.cert_data.cert_data; - let pem = parse_pem(&raw_cert_data.qe_cert_data.cert_data).expect("Failed to parse cert data"); - // Cert Chain: - // [0]: pck -> - // [1]: pck ca -> - // [2]: root ca - let cert_chain = parse_certchain(&pem); - let pck = &cert_chain[0]; + // // Cert Chain: + // // [0]: pck -> + // // [1]: pck ca -> + // // [2]: root ca + let ranges = cert_chain_processor::find_certificate_ranges(raw_cert_data); + if ranges.is_empty() { + return Err(TdxError::Dcap("No certificates found".to_string())); + } + let (pck_start, pck_end) = ranges[0]; + let (_, pem_struct) = parse_x509_pem(&raw_cert_data[pck_start..pck_end]) + .map_err(|e| TdxError::X509(format!("x509_parser error: {e}")))?; + let pck = pem_struct + .parse_x509() + .map_err(|e| TdxError::X509(format!("x509 error: {e}")))?; - let pck_issuer = get_x509_issuer_cn(pck); + let pck_issuer = get_x509_issuer_cn(&pck); let pck_ca = match pck_issuer.as_str() { "Intel SGX PCK Platform CA" => CA::PLATFORM, @@ -33,13 +43,19 @@ pub fn get_pck_fmspc_and_issuer(quote: &QuoteV4) -> (String, CA) { _ => panic!("Unknown PCK Issuer"), }; - let fmspc_slice = extract_fmspc_from_extension(pck); + let fmspc_slice = extract_fmspc_from_extension(&pck); let fmspc = hex::encode(fmspc_slice); - (fmspc, pck_ca) + Ok((fmspc, pck_ca)) +} + +fn get_x509_issuer_cn<'a>(cert: &'a X509Certificate<'a>) -> String { + let issuer = cert.issuer(); + let cn = issuer.iter_common_name().next().unwrap(); + cn.as_str().unwrap().to_string() } -pub fn extract_fmspc_from_extension<'a>(cert: &'a X509Certificate<'a>) -> [u8; 6] { +fn extract_fmspc_from_extension<'a>(cert: &'a X509Certificate<'a>) -> [u8; 6] { let sgx_extensions_bytes = cert .get_extension_unique(&oid!(1.2.840 .113741 .1 .13 .1)) .unwrap()