use crate::bindings::*; use crate::error::RaTlsError; use lazy_static::lazy_static; use log::{trace, warn}; use std::fmt::Debug; use std::ops::Deref; use std::sync::Mutex; use std::time::Instant; pub struct Quote { buf: Vec, report_body: *const sgx_report_body_t, } impl TryFrom> for Quote { type Error = RaTlsError; fn try_from(buf: Vec) -> Result { let report_body_offset = size_of::(); let report_body_size = size_of::(); if buf.len() < report_body_offset + report_body_size { let minimal = report_body_offset + report_body_size; let actual = buf.len(); return Err(RaTlsError::QuoteError(format!( "Failed to parse DCAP quote, min {minimal}, act {actual}" ))); } let report_body = buf.as_slice()[report_body_offset..].as_ptr() as *const sgx_report_body_t; Ok(Self { buf, report_body }) } } #[repr(C)] struct QuoteHeader { pub version: u16, pub att_key_type: u16, pub att_key_data_0: u32, pub qe_svn: u16, pub pce_svn: u16, pub vendor_id: [u8; 16], pub user_data: [u8; 20], } impl TryFrom<&[u8]> for Quote { type Error = RaTlsError; fn try_from(buf: &[u8]) -> Result { buf.to_vec().try_into() } } impl TryFrom for Quote { type Error = RaTlsError; fn try_from(value: ReportData) -> Result { Self::from_report_data(value) } } impl Deref for Quote { type Target = [u8]; fn deref(&self) -> &Self::Target { self.buf.as_ref() } } impl Quote { pub fn from_report_data(data: ReportData) -> Result { IOCTL_CLIENT .lock() .unwrap() .generate_quote(data)? .try_into() } pub fn from_slice(slice: &[u8]) -> Result { slice.try_into() } pub fn as_slice(&self) -> &[u8] { self } pub fn verify(&self) -> Result { IOCTL_CLIENT.lock().unwrap().verify_quote(self.buf.as_ref()) } pub fn isv_family_id(&self) -> sgx_isvfamily_id_t { unsafe { (*self.report_body).isv_family_id } } pub fn isv_ext_prod_id(&self) -> sgx_isvext_prod_id_t { unsafe { (*self.report_body).isv_ext_prod_id } } pub fn config_id(&self) -> sgx_config_id_t { unsafe { (*self.report_body).config_id } } pub fn mrenclave(&self) -> sgx_measurement_t { unsafe { (*self.report_body).mr_enclave } } pub fn mrsigner(&self) -> sgx_measurement_t { unsafe { (*self.report_body).mr_signer } } pub fn product_id(&self) -> sgx_prod_id_t { unsafe { (*self.report_body).isv_prod_id } } pub fn version(&self) -> sgx_isv_svn_t { unsafe { (*self.report_body).isv_svn } } pub fn report_data(&self) -> ReportData { unsafe { (*self.report_body).report_data.into() } } } impl Debug for Quote { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("SGXQuote") .field("mrenclave", &self.mrenclave()) .field("mrsigner", &self.mrsigner()) .field("report_body", &self.report_data()) .field("product_id", &self.product_id()) .field("version", &self.version()) .field("family_id", &self.isv_family_id()) .field("ext_prod_id", &self.isv_ext_prod_id()) .field("config_id", &self.config_id()) .finish() } } lazy_static! { pub static ref IOCTL_CLIENT: Mutex = { let client = IoctlClient::new(); Mutex::new(client) }; } pub struct IoctlClient { fd: HandleType, quote_size: Option, supplemental_size: Option, } // Needed to numb the compiler unsafe impl Send for IoctlClient {} impl IoctlClient { fn new() -> Self { Self { fd: std::ptr::null_mut(), quote_size: None, supplemental_size: None, } } fn handle(&mut self) -> Result { if self.fd.is_null() { let handle = unsafe { dcap_quote_open() }; if handle.is_null() { return Err(RaTlsError::DcapError( "Failed to open DCAP quote device".to_string(), )); } self.fd = handle; } Ok(self.fd) } fn get_quote_size(&mut self) -> Result { if self.quote_size.is_none() { let size = unsafe { dcap_get_quote_size(self.handle()?) }; trace!("DCAP quote size is {}", size); self.quote_size = Some(size); } Ok(*self.quote_size.as_ref().unwrap()) } fn get_supplemental_data_size(&mut self) -> Result { if self.supplemental_size.is_none() { let size = unsafe { dcap_get_supplemental_data_size(self.handle()?) }; trace!("DCAP supplemental data size is {}", size); self.quote_size = Some(size); } Ok(*self.supplemental_size.as_ref().unwrap()) } pub fn generate_quote(&mut self, report_data: ReportData) -> Result, RaTlsError> { let instant = Instant::now(); let quote_buf = self.generate_quote_inner(report_data)?; trace!("Generated quote in {:?}ms", instant.elapsed().as_millis()); Ok(quote_buf) } fn generate_quote_inner(&mut self, report_data: ReportData) -> Result, RaTlsError> { let quote_size = self.get_quote_size()?; let mut quote_buf: Vec = vec![0; quote_size as usize]; let instant = Instant::now(); let ret_code = unsafe { dcap_generate_quote(self.handle()?, quote_buf.as_mut_ptr(), &report_data.into()) }; trace!("Generated quote in {:?}ms", instant.elapsed().as_millis()); if ret_code < 0 { return Err(RaTlsError::DcapError( "Failed to generate DCAP quote".to_string(), )); } Ok(quote_buf) } pub fn verify_quote(&mut self, quote_buf: &[u8]) -> Result { let instant = Instant::now(); let result = self.verify_quote_inner(quote_buf)?; trace!("Verified quote in {:?}ms", instant.elapsed().as_millis()); if result.is_negligible() { if !result.is_ok() { warn!("DCAP quote verification returned: {:?}", result); } return Ok(result); } Err(RaTlsError::QuoteError(format!( "DCAP quote verification returned: {:?}", result ))) } fn verify_quote_inner(&mut self, quote_buf: &[u8]) -> Result { let supplemental_data_size = self.get_supplemental_data_size()?; let mut status = 1; let mut result = SGX_QL_QV_RESULT_UNSPECIFIED; let mut suppl_buf: Vec = vec![0; supplemental_data_size as usize]; let ret_code = unsafe { dcap_verify_quote( self.handle()?, quote_buf.as_ptr(), quote_buf.len() as u32, &mut status, &mut result, supplemental_data_size, suppl_buf.as_mut_ptr(), ) }; if ret_code < 0 { return Err(RaTlsError::DcapError( "Failed to verify DCAP quote".to_string(), )); } Ok(result.into()) } } impl Drop for IoctlClient { fn drop(&mut self) { unsafe { if !self.fd.is_null() { dcap_quote_close(self.fd); } } } } type HandleType = *mut ::std::os::raw::c_void; pub type ReportData = [u8; 64]; #[derive(Debug)] pub enum VerifyResult { Ok, ConfigNeeded, OutOfDate, OutOfDateConfigNeeded, InvalidSignature, Revoked, Unspecified, SwHardeningNeeded, ConfigAndSwHardeningNeeded, } impl From for VerifyResult { fn from(result: sgx_ql_qv_result_t) -> Self { match result { SGX_QL_QV_RESULT_OK => VerifyResult::Ok, SGX_QL_QV_RESULT_CONFIG_NEEDED => VerifyResult::ConfigNeeded, SGX_QL_QV_RESULT_OUT_OF_DATE => VerifyResult::OutOfDate, SGX_QL_QV_RESULT_OUT_OF_DATE_CONFIG_NEEDED => VerifyResult::OutOfDateConfigNeeded, SGX_QL_QV_RESULT_INVALID_SIGNATURE => VerifyResult::InvalidSignature, SGX_QL_QV_RESULT_REVOKED => VerifyResult::Revoked, SGX_QL_QV_RESULT_SW_HARDENING_NEEDED => VerifyResult::SwHardeningNeeded, SGX_QL_QV_RESULT_CONFIG_AND_SW_HARDENING_NEEDED => { VerifyResult::ConfigAndSwHardeningNeeded } //SGX_QL_QV_RESULT_UNSPECIFIED => QuoteVerifyResult::Unspecified, _ => VerifyResult::Unspecified, } } } impl VerifyResult { pub fn is_ok(&self) -> bool { match self { VerifyResult::Ok => true, _ => false, } } pub fn is_negligible(&self) -> bool { match self { VerifyResult::Ok => true, VerifyResult::ConfigNeeded => true, VerifyResult::OutOfDate => true, VerifyResult::OutOfDateConfigNeeded => true, VerifyResult::SwHardeningNeeded => true, VerifyResult::ConfigAndSwHardeningNeeded => true, _ => false, } } }