347 lines
9.8 KiB
Rust
347 lines
9.8 KiB
Rust
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<u8>,
|
|
report_body: *const sgx_report_body_t,
|
|
}
|
|
|
|
impl TryFrom<Vec<u8>> for Quote {
|
|
type Error = RaTlsError;
|
|
fn try_from(buf: Vec<u8>) -> Result<Self, Self::Error> {
|
|
let report_body_offset = size_of::<QuoteHeader>();
|
|
let report_body_size = size_of::<sgx_report_body_t>();
|
|
|
|
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<Self, Self::Error> {
|
|
buf.to_vec().try_into()
|
|
}
|
|
}
|
|
|
|
impl TryFrom<ReportData> for Quote {
|
|
type Error = RaTlsError;
|
|
|
|
fn try_from(value: ReportData) -> Result<Self, Self::Error> {
|
|
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<Self, RaTlsError> {
|
|
IOCTL_CLIENT
|
|
.lock()
|
|
.unwrap()
|
|
.generate_quote(data)?
|
|
.try_into()
|
|
}
|
|
|
|
pub fn from_slice(slice: &[u8]) -> Result<Self, RaTlsError> {
|
|
slice.try_into()
|
|
}
|
|
|
|
pub fn as_slice(&self) -> &[u8] {
|
|
self
|
|
}
|
|
|
|
pub fn verify(&self) -> Result<VerifyResult, RaTlsError> {
|
|
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<IoctlClient> = {
|
|
let client = IoctlClient::new();
|
|
Mutex::new(client)
|
|
};
|
|
}
|
|
|
|
// lazy_static! {
|
|
// pub static ref STATIC_QUOTE: once_cell::sync::OnceCell<Quote> =
|
|
// OnceCell::with_value(Quote::from_report_data([0u8; 64]).unwrap());
|
|
// }
|
|
// unsafe impl Send for Quote {}
|
|
|
|
lazy_static! {
|
|
pub static ref STATIC_QUOTE: Result<Quote, RaTlsError> = Quote::from_report_data([0u8; 64]);
|
|
}
|
|
|
|
unsafe impl Sync for Quote {}
|
|
|
|
pub struct IoctlClient {
|
|
fd: HandleType,
|
|
quote_size: Option<u32>,
|
|
supplemental_size: Option<u32>,
|
|
}
|
|
|
|
// 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<HandleType, RaTlsError> {
|
|
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<u32, RaTlsError> {
|
|
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<u32, RaTlsError> {
|
|
if self.supplemental_size.is_none() {
|
|
let size = unsafe { dcap_get_supplemental_data_size(self.handle()?) };
|
|
trace!("DCAP supplemental data size is {}", size);
|
|
self.supplemental_size = Some(size);
|
|
}
|
|
|
|
Ok(*self.supplemental_size.as_ref().unwrap())
|
|
}
|
|
|
|
pub fn generate_quote(&mut self, report_data: ReportData) -> Result<Vec<u8>, 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<Vec<u8>, RaTlsError> {
|
|
let quote_size = self.get_quote_size()?;
|
|
let mut quote_buf: Vec<u8> = 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<VerifyResult, RaTlsError> {
|
|
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<VerifyResult, RaTlsError> {
|
|
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<u8> = 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<sgx_ql_qv_result_t> 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,
|
|
}
|
|
}
|
|
}
|