Skip to content
Merged
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
259 changes: 191 additions & 68 deletions Cargo.lock

Large diffs are not rendered by default.

12 changes: 11 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,14 @@ tempfile = "3.4.0"
tonic = "0.12"
tonic-build = "0.12"
serde_yaml = "0.9"
zeroize = { version = "1.7", features = ["derive"] }
zeroize = { version = "1.7", features = ["derive"] }

# dcap-qvl fork adding a `QuoteVerifier::allow_expired` option, used by the
# pure-Rust TDX verifier backend (feature `tdx-dcap-rust`) to treat expired
# collateral as non-fatal, matching the Intel DCAP QVL (FFI) backend. The fork
# only relaxes the freshness check; signature/chain verification is unchanged.
# See https://github.com/jialez0/dcap-qvl/tree/v0.4.1-allow-expired
# (based on Phala-Network/dcap-qvl v0.4.1). This patch is inert for the default
# build, which does not enable `tdx-dcap-rust` and never compiles dcap-qvl.
[patch.crates-io]
dcap-qvl = { git = "https://github.com/jialez0/dcap-qvl", branch = "v0.4.1-allow-expired" }
10 changes: 9 additions & 1 deletion attestation-service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ edition = "2021"
[features]
default = [ "restful-bin", "rvps-grpc", "all-verifier" ]
all-verifier = [ "verifier/all-verifier" ]
tdx-verifier = [ "verifier/tdx-verifier" ]
# Like `all-verifier`, but the TDX verifier uses the dcap-qvl backend and SGX is
# dropped (it would still link the DCAP shared library). See
# deps/verifier/src/tdx/verify/native.rs.
all-verifier-rust = [ "verifier/all-verifier-rust" ]
# `tdx-verifier` keeps its historical meaning (DCAP shared library backend) for
# backward compatibility. The dcap-qvl backend is opt-in via `tdx-dcap-rust`.
tdx-verifier = [ "verifier/tdx-dcap-ffi" ]
tdx-dcap-ffi = [ "verifier/tdx-dcap-ffi" ]
tdx-dcap-rust = [ "verifier/tdx-dcap-rust" ]
sgx-verifier = [ "verifier/sgx-verifier" ]
az-snp-vtpm-verifier = [ "verifier/az-snp-vtpm-verifier" ]
az-tdx-vtpm-verifier = [ "verifier/az-tdx-vtpm-verifier" ]
Expand Down
6 changes: 6 additions & 0 deletions attestation-service/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ BIN_NAMES := grpc-as restful-as
DEBUG ?=
DESTDIR ?= $(PREFIX)/bin

# Verifier feature set. The default keeps the TDX verifier on the Intel DCAP
# shared library (libsgx_dcap_quoteverify) backend, so the build is unchanged.
#
# To build the shared-library-free TDX backend (dcap-qvl) instead, use:
# make VERIFIER=all-verifier-rust
# It builds on the same Rust toolchain as the default build.
VERIFIER ?= all-verifier

RVPS_GRPC := true
Expand Down
52 changes: 49 additions & 3 deletions deps/verifier/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,59 @@ edition = "2021"
[features]
default = ["all-verifier"]
all-verifier = [
"tdx-verifier",
"tdx-dcap-ffi",
"sgx-verifier",
"snp-verifier",
"csv-verifier",
"hygon-dcu-verifier",
"system-verifier",
"tpm-verifier",
]
# Same set as `all-verifier`, but the TDX verifier uses the dcap-qvl backend
# instead of the DCAP shared library. `sgx-verifier` is intentionally omitted:
# it still links `libsgx_dcap_quoteverify` via FFI, which would defeat the
# purpose of a shared-library-free build.
all-verifier-rust = [
"tdx-dcap-rust",
"snp-verifier",
"csv-verifier",
"hygon-dcu-verifier",
"system-verifier",
"tpm-verifier",
]
# TDX quote verification. The `tdx-verifier` umbrella pulls in everything the
# TDX verifier needs *except* the actual quote-verification backend, which must
# be selected separately and exactly once:
#
# * tdx-dcap-ffi - DCAP QVL via FFI, links `libsgx_dcap_quoteverify` (default)
# * tdx-dcap-rust - dcap-qvl, no external DCAP shared library
#
# Enabling `tdx-verifier` on its own (no backend) is a compile error. The
# default build reaches TDX through `all-verifier` -> `tdx-dcap-ffi`, so its
# behaviour is unchanged.
tdx-verifier = [
"scroll",
"intel-tee-quote-verification-rs",
"gpu-verifier",
"openssl",
]
tdx-dcap-ffi = ["tdx-verifier", "intel-tee-quote-verification-rs"]
# Backend built on `dcap-qvl` (with its `ring` crypto backend). Removes the
# dependency on the Intel DCAP shared library (`libsgx_dcap_quoteverify`) and
# its dynamically loaded quote provider. Verification collateral (TCB info, QE
# identity, CRLs) is fetched over HTTPS from a PCCS using the verifier's existing
# reqwest stack. Builds on the same Rust toolchain as the default (FFI) build;
# see deps/verifier/src/tdx/verify/native.rs.
tdx-dcap-rust = [
"tdx-verifier",
"dcap-qvl",
"x509-parser",
"asn1-rs",
"urlencoding",
"chrono",
]
sgx-verifier = ["scroll", "intel-tee-quote-verification-rs"]
az-snp-vtpm-verifier = ["az-snp-vtpm", "sev", "snp-verifier"]
az-tdx-vtpm-verifier = ["az-tdx-vtpm", "openssl", "tdx-verifier"]
az-tdx-vtpm-verifier = ["az-tdx-vtpm", "openssl", "tdx-dcap-ffi"]
snp-verifier = ["asn1-rs", "openssl", "sev", "x509-parser", "reqwest"]
csv-verifier = ["codicon", "csv-rs", "openssl", "tokio/fs"]
hygon-dcu-verifier = ["csv-rs"]
Expand Down Expand Up @@ -87,6 +123,16 @@ sha2.workspace = true
sm3 = "0.4.2"
tokio = { workspace = true, optional = true, default-features = false }
intel-tee-quote-verification-rs = { version = "0.3.0", optional = true }
# TDX/SGX DCAP quote verification (used with its `ring` backend), by the `tdx-dcap-rust`
# backend. Only the offline `verify` core is used (no `report` feature), so no
# aws-lc / reqwest 0.13 is pulled in; collateral is fetched with the verifier's
# own reqwest. Pinned to 0.4.1 for MSRV/dependency-tree stability.
dcap-qvl = { version = "=0.4.1", default-features = false, features = [
"std",
"ring",
"default-x509",
], optional = true }
urlencoding = { version = "2", optional = true }
strum.workspace = true
tss-esapi = { version = "7.4.0", optional = true }
uuid = { version = "1.0", features = ["v4"], optional = true }
Expand Down
4 changes: 3 additions & 1 deletion deps/verifier/src/tdx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ use crate::tdx::claims::generate_parsed_claim;
use super::*;
use async_trait::async_trait;
use base64::Engine;
use quote::{ecdsa_quote_verification, parse_tdx_quote};
use quote::parse_tdx_quote;
use serde::{Deserialize, Serialize};
use verify::ecdsa_quote_verification;

use serde_json::Value;

pub(crate) mod claims;
pub(crate) mod gpu;
pub(crate) mod quote;
pub(crate) mod verify;

use crate::tdx::gpu::GpuEvidenceList;

Expand Down
216 changes: 4 additions & 212 deletions deps/verifier/src/tdx/quote.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,7 @@
use anyhow::{anyhow, bail, Result};
use core::fmt;
use log::{debug, warn};
use qvl::{
quote3_error_t, sgx_ql_qv_result_t, sgx_ql_qv_supplemental_t, sgx_ql_request_policy_t,
sgx_qv_set_enclave_load_policy, tee_get_supplemental_data_version_and_size,
tee_qv_get_collateral, tee_supp_data_descriptor_t, tee_verify_quote,
};
use scroll::Pread;
use serde::Serialize;
use std::mem;
use std::time::{Duration, SystemTime};

use intel_tee_quote_verification_rs as qvl;

/// Human-readable TCB verification status string.
fn qv_result_to_str(result: sgx_ql_qv_result_t) -> &'static str {
match result {
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OK => "UpToDate",
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_CONFIG_NEEDED => "ConfigurationNeeded",
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OUT_OF_DATE => "OutOfDate",
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OUT_OF_DATE_CONFIG_NEEDED => {
"OutOfDateConfigurationNeeded"
}
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_SW_HARDENING_NEEDED => "SWHardeningNeeded",
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_CONFIG_AND_SW_HARDENING_NEEDED => {
"ConfigurationAndSWHardeningNeeded"
}
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_INVALID_SIGNATURE => "InvalidSignature",
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_REVOKED => "Revoked",
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_UNSPECIFIED => "Unspecified",
_ => "Unknown",
}
}

/// Captures the TCB verification result and supplemental data from DCAP QVL,
/// so that upper-layer policy engines can make fine-grained attestation decisions.
Expand Down Expand Up @@ -376,6 +346,10 @@ impl Quote {
body_field!(rtmr_1);
body_field!(rtmr_2);
body_field!(rtmr_3);
// TDX TEE TCB SVN (16 bytes) from the TD report. Used by the dcap-qvl
// quote-verification backend to reproduce Intel QVL's TCB level matching.
#[cfg(feature = "tdx-dcap-rust")]
body_field!(tcb_svn);
}

impl fmt::Display for Quote {
Expand Down Expand Up @@ -456,147 +430,6 @@ pub fn parse_tdx_quote(quote_bin: &[u8]) -> Result<Quote> {
}
}

pub async fn ecdsa_quote_verification(quote: &[u8]) -> Result<TcbVerificationResult> {
let mut supp_data: sgx_ql_qv_supplemental_t = Default::default();
let mut supp_data_desc = tee_supp_data_descriptor_t {
major_version: 0,
data_size: 0,
p_data: &mut supp_data as *mut sgx_ql_qv_supplemental_t as *mut u8,
};

// Call DCAP quote verify library to set QvE loading policy to multi-thread
// We only need to set the policy once; otherwise, it will return the error code 0xe00c (SGX_QL_UNSUPPORTED_LOADING_POLICY)
static INIT: std::sync::Once = std::sync::Once::new();
INIT.call_once(|| {
match sgx_qv_set_enclave_load_policy(
sgx_ql_request_policy_t::SGX_QL_PERSISTENT_QVE_MULTI_THREAD,
) {
quote3_error_t::SGX_QL_SUCCESS => {
debug!("Info: sgx_qv_set_enclave_load_policy successfully returned.")
}
err => warn!(
"Error: sgx_qv_set_enclave_load_policy failed: {:#04x}",
err as u32
),
}
});

match tee_get_supplemental_data_version_and_size(quote) {
Ok((supp_ver, supp_size)) => {
if supp_size == mem::size_of::<sgx_ql_qv_supplemental_t>() as u32 {
debug!("tee_get_quote_supplemental_data_version_and_size successfully returned.");
debug!(
"Info: latest supplemental data major version: {}, minor version: {}, size: {}",
u16::from_be_bytes(supp_ver.to_be_bytes()[..2].try_into()?),
u16::from_be_bytes(supp_ver.to_be_bytes()[2..].try_into()?),
supp_size,
);
supp_data_desc.data_size = supp_size;
} else {
warn!("Quote supplemental data size is different between DCAP QVL and QvE, please make sure you installed DCAP QVL and QvE from same release.")
}
}
Err(e) => bail!(
"tee_get_quote_supplemental_data_size failed: {:#04x}",
e as u32
),
}

// get collateral
let collateral = match tee_qv_get_collateral(quote) {
Ok(c) => {
debug!("tee_qv_get_collateral successfully returned.");
Some(c)
}
Err(e) => {
warn!("tee_qv_get_collateral failed: {:#04x}", e as u32);
None
}
};

// set current time. This is only for sample purposes, in production mode a trusted time should be used.
//
let current_time = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs() as i64;

let p_supplemental_data = match supp_data_desc.data_size {
0 => None,
_ => Some(&mut supp_data_desc),
};

// call DCAP quote verify library for quote verification
let (collateral_expiration_status, quote_verification_result) = tee_verify_quote(
quote,
collateral.as_ref(),
current_time,
None,
p_supplemental_data,
)
.map_err(|e| anyhow!("tee_verify_quote failed: {:#04x}", e as u32))?;

debug!("tee_verify_quote successfully returned.");

// check verification result
match quote_verification_result {
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OK => {
// check verification collateral expiration status
// this value should be considered in your own attestation/verification policy
if collateral_expiration_status == 0 {
debug!("Verification completed successfully.");
} else {
warn!("Verification completed, but collateral is out of date based on 'expiration_check_date' you provided.");
}
}
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_CONFIG_NEEDED
| sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OUT_OF_DATE
| sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OUT_OF_DATE_CONFIG_NEEDED
| sgx_ql_qv_result_t::SGX_QL_QV_RESULT_SW_HARDENING_NEEDED
| sgx_ql_qv_result_t::SGX_QL_QV_RESULT_CONFIG_AND_SW_HARDENING_NEEDED => {
warn!(
"Verification completed with Non-terminal result: {:x}",
quote_verification_result as u32
);
}
_ => {
bail!(
"Verification completed with Terminal result: {:x}",
quote_verification_result as u32
);
}
}

// Extract advisory IDs from supplemental data (null-terminated C string).
// sa_list is [c_char; 320] (i8 on Linux), containing comma-separated advisory IDs.
let advisory_ids = {
let sa_bytes: Vec<u8> = supp_data
.sa_list
.iter()
.take_while(|&&b| b != 0)
.map(|&b| b as u8)
.collect();
String::from_utf8_lossy(&sa_bytes).to_string()
};

let result = TcbVerificationResult {
tcb_status: qv_result_to_str(quote_verification_result).to_string(),
tcb_status_code: quote_verification_result as u32,
collateral_expired: collateral_expiration_status != 0,
earliest_issue_date: supp_data.earliest_issue_date,
latest_issue_date: supp_data.latest_issue_date,
earliest_expiration_date: supp_data.earliest_expiration_date,
tcb_level_date_tag: supp_data.tcb_level_date_tag,
tcb_eval_ref_num: supp_data.tcb_eval_ref_num,
advisory_ids,
tee_type: supp_data.tee_type,
};

debug!("TCB verification result: {:?}", result);

Ok(result)
}

#[cfg(test)]
mod tests {
use rstest::rstest;
Expand All @@ -616,45 +449,4 @@ mod tests {

let _ = fs::write(format!("{quote_path}.txt"), parsed_quote);
}

/// Test to verify the TDX quote, both in v4 and v5 format.
///
/// This unit test requires two packages, s.t. `libsgx-dcap-quote-verify-dev` and `libsgx-dcap-default-qpl`
/// On ubuntu 22.04, you need to run the following scripts to install.
/// ```shell
/// curl -L https://download.01.org/intel-sgx/sgx_repo/ubuntu/intel-sgx-deb.key | tee intel-sgx-deb.key | apt-key add - && \
/// echo 'deb [arch=amd64] https://download.01.org/intel-sgx/sgx_repo/ubuntu jammy main' | tee /etc/apt/sources.list.d/intel-sgx.list && \
/// apt-get update && \
/// apt-get install -y libsgx-dcap-default-qpl libsgx-dcap-quote-verify
/// ```
///
/// Also, you need to configure DCAP to work with alibaba cloud's PCCS.
/// edit `/etc/sgx_default_qcnl.conf` and replace the whole content with
/// ```json
/// {"pccs_url" :"https://sgx-dcap-server.cn-beijing.aliyuncs.com/sgx/certification/v4/"}
/// ```
///
/// The manual modification upon `sgx_default_qcnl.conf` could be promoted after
/// https://github.com/intel/SGXDataCenterAttestationPrimitives/issues/409 is resolved.
///
/// Finally, DCAP only provides packages on x86-64 platform, thus we only test this on x86-64
/// platforms.
#[cfg(target_arch = "x86_64")]
#[rstest]
#[ignore]
#[tokio::test]
#[case("./test_data/tdx_quote_4.dat")]
#[ignore]
#[tokio::test]
#[case("./test_data/tdx_quote_5.dat")]
async fn test_verify_tdx_quote(#[case] quote: &str) {
let quote_bin = fs::read(quote).unwrap();
let res = ecdsa_quote_verification(quote_bin.as_slice()).await;
assert!(res.is_ok(), "{res:?}");
let tcb_result = res.unwrap();
println!(
"TCB status: {}, advisory_ids: {}, tcb_level_date_tag: {}",
tcb_result.tcb_status, tcb_result.advisory_ids, tcb_result.tcb_level_date_tag
);
}
}
Loading
Loading