Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
3 changes: 2 additions & 1 deletion tdx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 }
4 changes: 2 additions & 2 deletions tdx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
```
```
12 changes: 10 additions & 2 deletions tdx/examples/attestation.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
use dcap_rs::types::quote::Quote;
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();
// ================================================================================
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!");
}
11 changes: 6 additions & 5 deletions tdx/examples/fmspc.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
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");
} else {
println!("Platform: TDX");
}
println!("Version: {}", report.header.version);
}
}
40 changes: 11 additions & 29 deletions tdx/examples/inspect.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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(&quote_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(&quote_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(&quote.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(())
}
Binary file added tdx/examples/testdata/tdx_v5_quote.bin
Binary file not shown.
7 changes: 1 addition & 6 deletions tdx/src/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -44,11 +44,6 @@ impl Device {
Ok(Device { options, provider })
}

pub fn get_attestation_report(&self) -> Result<(QuoteV4, Option<Vec<u8>>)> {
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<u8>, Option<Vec<u8>>)> {
let report_data = match self.provider.device_type {
CocoDeviceType::Tpm => {
Expand Down
6 changes: 6 additions & 0 deletions tdx/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,9 @@ impl From<anyhow::Error> for TdxError {
TdxError::Anyhow(err.to_string())
}
}

impl From<std::str::Utf8Error> for TdxError {
fn from(err: std::str::Utf8Error) -> Self {
TdxError::IO(format!("{:?}", err))
}
}
88 changes: 32 additions & 56 deletions tdx/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,26 @@ 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 {
pub fn new() -> Self {
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<u8>` 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<Vec<u8>>)> {
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<Vec<u8>>)> {
let device = device::Device::new(options)?;
device.get_attestation_report()
}

/// Retrieve an Attestation Report in raw bytes.
///
/// Returns:
Expand All @@ -69,58 +47,56 @@ 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))?;
if root_ca.is_empty() || root_ca_crl.is_empty() {
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(())
}
}

Expand Down
52 changes: 34 additions & 18 deletions tdx/src/utils.rs
Original file line number Diff line number Diff line change
@@ -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]> {
Expand All @@ -14,32 +12,50 @@ 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(&quote.signature.qe_cert_data.cert_data);
pub fn der_to_pem_bytes(der_bytes: &[u8]) -> Vec<u8> {
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 = &quote.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,
"Intel SGX PCK Processor CA" => CA::PROCESSOR,
_ => 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()
Expand Down