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 @@ -5,5 +5,6 @@ members = [
"crates/quote-verifier",
"crates/pcs",
"crates/collaterals",
"./livyimpl",
]
exclude = ["zkvm/risc0", "zkvm/risc0/guest"]
26 changes: 17 additions & 9 deletions crates/pcs/src/client.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anyhow::{anyhow, bail, Error};
use dcap_quote_verifier::cert::{
is_sgx_pck_platform_ca_dn, is_sgx_pck_processor_ca_dn, parse_certchain,
is_sgx_pck_platform_ca_dn, is_sgx_pck_processor_ca_dn, parse_certchain, parse_der_certchain,
};
use dcap_quote_verifier::collateral::QvCollateral;
use dcap_quote_verifier::sgx_extensions::extract_sgx_extensions;
Expand Down Expand Up @@ -66,21 +66,29 @@ impl PCSClient {
} else {
format!("{pcs_url}/tdx/certification/v4")
};
if qe_cert_data.cert_data_type != 5 {
bail!("QE Cert Type must be 5".to_string());
if ![4, 5].contains(&qe_cert_data.cert_data_type) {
bail!("QE Cert Type must be 4 or 5".to_string());
}
let certchain_pems = parse_pem(&qe_cert_data.cert_data)
.map_err(|e| anyhow!("cannot parse QE cert chain: {}", e))?;

let certchain = if qe_cert_data.cert_data_type == 5 {
// Type 5: PEM format
let certchain_pems = parse_pem(&qe_cert_data.cert_data)
.map_err(|e| anyhow!("cannot parse QE cert chain: {}", e))?;
parse_certchain(&certchain_pems)
.map_err(|e| anyhow!("cannot parse QE cert chain: {}", e))?
} else {
// Type 4: DER format
parse_der_certchain(&qe_cert_data.cert_data)
.map_err(|e| anyhow!("cannot parse QE cert chain: {}", e))?
};

let certchain = parse_certchain(&certchain_pems)
.map_err(|e| anyhow!("cannot parse QE cert chain: {}", e))?;
if certchain.len() != 3 {
bail!("QE Cert chain must have 3 certs".to_string());
}

// get the pck certificate
let pck_cert = &certchain[0];
let pck_cert_issuer = &certchain[1];
let pck_cert = certchain[0].as_x509();
let pck_cert_issuer = certchain[1].as_x509();

// get the SGX extension
let sgx_extensions = extract_sgx_extensions(pck_cert)
Expand Down
3 changes: 3 additions & 0 deletions crates/quote-verifier/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ sha3 = { version = "0.10.8" }
alloy-sol-types = { version = "0.8.12" }
dcap-types = { path = "../types" }


[features]
default = []

[dev-dependencies]
serde_json = { version = "1.0", features = ["preserve_order"] }
dcap-collaterals = { path = "../collaterals" }
dcap-pcs = { path = "../pcs" }
chrono = "0.4.41"
58 changes: 54 additions & 4 deletions crates/quote-verifier/src/cert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,62 @@ impl BitAnd for KeyUsageFlags {
}
}

#[derive(Clone, Debug)]
pub struct OwnedX509Certificate {
der: Vec<u8>,
cert: X509Certificate<'static>,
}

impl OwnedX509Certificate {
pub fn new(der: &[u8]) -> crate::Result<Self> {
let (rest, cert) = X509Certificate::from_der(der)?;
if !rest.is_empty() {
bail!("Extra data after certificate");
}
let owned_cert = unsafe { std::mem::transmute::<X509Certificate, X509Certificate<'static>>(cert) };
Ok(Self {
der: der.to_vec(),
cert: owned_cert,
})
}

pub fn as_x509(&self) -> &X509Certificate<'static> {
&self.cert
}
}

/// Parse a PEM-encoded certificate chain into a vector of `X509Certificate`.
pub fn parse_certchain(pem_certs: &[Pem]) -> crate::Result<Vec<X509Certificate>> {
Ok(pem_certs
pub fn parse_certchain(pem_certs: &[Pem]) -> crate::Result<Vec<OwnedX509Certificate>> {
pem_certs
.iter()
.map(|pem| pem.parse_x509())
.collect::<Result<_, _>>()?)
.map(|pem| OwnedX509Certificate::new(&pem.contents))
.collect::<Result<_, _>>()
}

/// Parse a DER-encoded certificate chain into a vector of `X509Certificate`.
/// This is used for Type 4 certificates which contain concatenated DER certificates.
pub fn parse_der_certchain(der_data: &[u8]) -> crate::Result<Vec<OwnedX509Certificate>> {
let mut certs = Vec::new();
let mut remaining = der_data;

while !remaining.is_empty() {
match OwnedX509Certificate::new(remaining) {
Ok(owned_cert) => {
let len = owned_cert.der.len();
certs.push(owned_cert);
remaining = &remaining[len..];
}
Err(_) => {
break;
}
}
}

if certs.is_empty() {
bail!("No valid certificates found in DER data");
}

Ok(certs)
}

/// Verifies the signature of a certificate using the public key of the signer certificate.
Expand Down
62 changes: 49 additions & 13 deletions crates/quote-verifier/src/quotes.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
pub mod version_3;
pub mod version_4;

use crate::cert::{parse_certchain, verify_crl_signature};
use crate::cert::{parse_certchain, parse_der_certchain, verify_crl_signature};
use crate::collateral::QvCollateral;
use crate::crl::IntelSgxCrls;
use crate::crypto::{sha256sum, verify_p256_signature_bytes};
Expand All @@ -24,6 +24,7 @@ use dcap_types::EnclaveIdentityV2TcbStatus;
use version_3::verify_quote_v3;
use version_4::verify_quote_v4;
use x509_parser::certificate::X509Certificate;
use dcap_types::quotes::QeReportCertData;

/// Verify the quote with the given collateral data and return the verification output.
///
Expand Down Expand Up @@ -93,23 +94,58 @@ fn verify_quote_common(
current_time: u64,
) -> Result<(QeTcb, SgxExtensions, TcbInfo, Validity)> {
// get the certchain embedded in the ecda quote signature data
// this can be one of 5 types, and we only support type 5
// this can be one of 5 types, and we support types 4 and 5
// https://github.com/intel/SGXDataCenterAttestationPrimitives/blob/aa239d25a437a28f3f4de92c38f5b6809faac842/QuoteGeneration/quote_wrapper/common/inc/sgx_quote_3.h#L63C4-L63C112
if qe_cert_data.cert_data_type != 5 {
bail!("QE Cert Type must be 5");
if ![4, 5].contains(&qe_cert_data.cert_data_type) {
bail!("QE Cert Type must be 4 or 5");
}
let certchain_pems = parse_pem(&qe_cert_data.cert_data)?;
let (pck_leaf_cert, pck_issuer_cert) = {
let mut certchain = parse_certchain(&certchain_pems)?;
// certchain in the cert_data whose type is 5 should have 3 certificates:
// PCK leaf, PCK issuer, and Root CA
if certchain.len() != 3 {
bail!("Invalid Certchain length");

// Update the parsing logic to handle type 6 recursively

let (pck_leaf_cert, pck_issuer_cert) = match qe_cert_data.cert_data_type {
5 => {
// Type 5: PEM format
let certchain_pems = parse_pem(&qe_cert_data.cert_data)?;
let certchain = parse_certchain(&certchain_pems)?;
if certchain.len() != 3 {
bail!("Invalid Certchain length for Type 5");
}
(certchain[0].clone(), certchain[1].clone())
}
4 => {
// Type 4: DER format
let certchain = parse_der_certchain(&qe_cert_data.cert_data)?;
if certchain.len() != 3 {
bail!("Invalid Certchain length for Type 4");
}
(certchain[0].clone(), certchain[1].clone())
}
// extract the leaf and issuer certificates, but ignore the root cert as we already have it
(certchain.remove(0), certchain.remove(0))
6 => {
// Type 6: QeReportCertData - recursive parsing
let qe_report_cert_data = QeReportCertData::from_bytes(&qe_cert_data.cert_data)?;
// Recursively parse the nested cert_data
let nested_cert_type = qe_report_cert_data.qe_cert_data.cert_data_type;
if ![4, 5].contains(&nested_cert_type) {
bail!("Nested QE Cert Type in Type 6 must be 4 or 5");
}
let nested_cert_data = &qe_report_cert_data.qe_cert_data.cert_data;
let certchain = if nested_cert_type == 5 {
let certchain_pems = parse_pem(nested_cert_data)?;
parse_certchain(&certchain_pems)?
} else {
parse_der_certchain(nested_cert_data)?
};
if certchain.len() != 3 {
bail!("Invalid Certchain length for Type 6");
}
(certchain[0].clone(), certchain[1].clone())
}
_ => bail!("QE Cert Type must be 4, 5, or 6"),
};

let pck_leaf_cert = pck_leaf_cert.as_x509();
let pck_issuer_cert = pck_issuer_cert.as_x509();

let intel_sgx_root_cert = collateral.get_sgx_intel_root_ca()?;
let intel_crls = {
let sgx_root_ca_crl = collateral.get_sgx_intel_root_ca_crl()?;
Expand Down
1 change: 1 addition & 0 deletions crates/quote-verifier/src/quotes/livyquotes/livy.json

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions crates/quote-verifier/src/quotes/version_4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ fn validate_quote_header_v4(quote_header: &QuoteHeader) -> Result<()> {
mod tests {
use super::*;
use dcap_types::utils::pem_to_der;
use chrono::Utc;
use dcap_pcs::client::PCSClient;

#[test]
fn test_verify_quote_v4_intel() {
Expand Down Expand Up @@ -179,4 +181,29 @@ mod tests {
let vo = res.unwrap();
assert_eq!(verified_output, vo);
}

#[test]
fn test_livy_quote_v4() {
#[derive(serde::Deserialize)]
struct LivyAttestation {
quote: String,
}

let attestation: LivyAttestation = serde_json::from_str(&include_str!("./livyquotes/livy.json")).unwrap();
let hex_quote = hex::decode(attestation.quote).unwrap();
let quote = QuoteV4::from_bytes(&hex_quote).unwrap().0;
let raw_collateral = PCSClient::default().get_collateral(false, &quote.signature.qe_cert_data).unwrap();
let collaterals = QvCollateral {
tcb_info_json: raw_collateral.tcb_info_json,
qe_identity_json: raw_collateral.qe_identity_json,
sgx_intel_root_ca_der: raw_collateral.sgx_intel_root_ca_der,
sgx_tcb_signing_der: raw_collateral.sgx_tcb_signing_der,
sgx_intel_root_ca_crl_der: raw_collateral.sgx_intel_root_ca_crl_der,
sgx_pck_crl_der: raw_collateral.sgx_pck_crl_der,
};

let current_time = Utc::now().timestamp().try_into().unwrap();
let res = verify_quote_v4(&quote, &collaterals, current_time);
assert!(res.is_ok(), "{:?}", res);
}
}
27 changes: 27 additions & 0 deletions livyimpl/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[package]
name = "livyimpl"
version = "0.1.0"
edition = "2021"

[dependencies]
dcap-types = { path = "../crates/types" }
dcap-quote-verifier = { path = "../crates/quote-verifier" }
dcap-pcs = { path = "../crates/pcs" }
dcap-collaterals = { path = "../crates/collaterals" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
hex = "0.4"
chrono = "0.4"
anyhow = "1"
tokio = { version = "1.0", features = ["full"] }
dcap-qvl = "0.3.2"
pem = "3.0"
rustls-webpki = "0.102.8"
base64 = "0.22.1"




[[bin]]
name = "livyimpl"
path = "src/main.rs"
Binary file added livyimpl/TrustedRootCA.der
Binary file not shown.
83 changes: 83 additions & 0 deletions livyimpl/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use dcap_quote_verifier::{
collateral::QvCollateral,
quotes::version_4::verify_quote_v4,
};
use dcap_types::quotes::version_4::QuoteV4;
use serde::Deserialize;
use chrono::Utc;
use dcap_pcs::client::PCSClient;
use anyhow::Context;

#[derive(Deserialize)]
struct LivyAttestation {
quote: String,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Testing Livy Quote V4 verification...");

// Load the livy quote from the JSON file
let attestation: LivyAttestation = serde_json::from_str(
include_str!("../../crates/quote-verifier/src/quotes/livyquotes/livy.json")
)?;

// Decode the hex quote
let hex_quote = hex::decode(&attestation.quote)?;
let quote = QuoteV4::from_bytes(&hex_quote)?.0;

println!("Quote parsed successfully");
println!("Quote header version: {}", quote.header.version);
println!("Quote header TEE type: {}", quote.header.tee_type);
println!("Quote header att key type: {}", quote.header.att_key_type);
println!("Quote header QE vendor ID: {:?}", quote.header.qe_vendor_id);
println!("Quote cert data type: {}", quote.signature.qe_cert_data.cert_data_type);

// Get collateral using PCSClient (same as working test)
let raw_collateral = PCSClient::default().get_collateral(false, &quote.signature.qe_cert_data)?;

println!("Quote Collateral tcb info: {:?}", raw_collateral.tcb_info_json);
println!("QE Identity: {:?}", raw_collateral.qe_identity_json);

// Use the collateral directly (no transformation needed)
let collateral = QvCollateral {
tcb_info_json: raw_collateral.tcb_info_json,
qe_identity_json: raw_collateral.qe_identity_json,
sgx_intel_root_ca_der: raw_collateral.sgx_intel_root_ca_der,
sgx_tcb_signing_der: raw_collateral.sgx_tcb_signing_der,
sgx_intel_root_ca_crl_der: raw_collateral.sgx_intel_root_ca_crl_der,
sgx_pck_crl_der: raw_collateral.sgx_pck_crl_der,
};

println!("Collateral created successfully");
println!("Root CA DER length: {}", collateral.sgx_intel_root_ca_der.len());
println!("TCB signing cert length: {}", collateral.sgx_tcb_signing_der.len());

// Get current timestamp
let current_time = Utc::now().timestamp().try_into()?;

// Verify the quote
let res = verify_quote_v4(&quote, &collateral, current_time);

if res.is_ok() {
println!("Quote verification successful!");
let output = res.unwrap();
println!("Status: {:?}", output.status);
println!("TEE Type: {:?}", output.tee_type);
} else {
println!("Quote verification failed: {:?}", res.err());
}

Ok(())
}

pub fn extract_raw_certs(cert_chain: &[u8]) -> Result<Vec<Vec<u8>>, Box<dyn std::error::Error>> {
Ok(pem::parse_many(cert_chain)
.context("Failed to parse certs")?
.iter()
.map(|i| i.contents().to_vec())
.collect())
}

pub fn extract_certs<'a>(cert_chain: &'a [u8]) -> Result<Vec<Vec<u8>>, Box<dyn std::error::Error>> {
extract_raw_certs(cert_chain)
}
Binary file modified zkvm/risc0/artifacts/dcap-quote-verifier
Binary file not shown.
4 changes: 2 additions & 2 deletions zkvm/risc0/src/methods.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

pub const DCAP_QUOTE_VERIFIER_ID: [u32; 8] = [1519563173, 766144868, 376996947, 1575798712, 1430650633, 1722721411, 708861073, 2650936882];
pub const DCAP_QUOTE_VERIFIER_ID_STR: &str = "a5b1925a6471aa2d53847816b8c7ec5d09ff455583a4ae66915c402a3216029e";
pub const DCAP_QUOTE_VERIFIER_ID: [u32; 8] = [117788011, 3653947158, 2684564209, 296244330, 1777556421, 2743419902, 3558065736, 916374292];
pub const DCAP_QUOTE_VERIFIER_ID_STR: &str = "6b4d050716cfcad9f13203a06a54a811c55bf369fe4385a348c613d414c39e36";
pub const DCAP_QUOTE_VERIFIER_ELF: &[u8] = include_bytes!("../artifacts/dcap-quote-verifier");