detee-sgx/src/quote.rs

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,
}
}
}