use byteorder::{BigEndian, ReadBytesExt};
use serde::{Serialize, Deserialize};
use sgx_isa::{Attributes, Miscselect, Report};
use std::str;
use std::fmt;
use std::marker::PhantomData;
#[cfg(feature = "client")]
use {
super::verifier::{self, Crypto, Error, ErrorKind},
pkix::types::DerSequence,
};
#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub enum IasVersion {
V2 = 2,
V3,
V4
}
pub const LATEST_IAS_VERSION: IasVersion = IasVersion::V4;
mod serde_option_bytes {
use serde_bytes;
use serde::de::{Deserializer, Visitor};
use serde::ser::Serializer;
use std::marker::PhantomData;
pub fn serialize<T, S>(value: &Option<T>, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
where T: serde_bytes::Serialize,
S: Serializer
{
match *value {
Some(ref bytes) => serde_bytes::serialize(bytes, serializer),
None => serializer.serialize_none(),
}
}
pub fn deserialize<'de, T, D>(deserializer: D) -> ::std::result::Result<Option<T>, D::Error>
where D: Deserializer<'de>,
T: serde_bytes::Deserialize<'de>
{
struct VisitorImpl<'de, T: serde_bytes::Deserialize<'de>>(PhantomData<&'de ()>, PhantomData<T>);
impl<'de, T> Visitor<'de> for VisitorImpl<'de, T>
where T: serde_bytes::Deserialize<'de>
{
type Value = Option<T>;
fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
write!(formatter, "an optional byte array")
}
fn visit_none<E>(self) -> Result<Self::Value, E> {
Ok(None)
}
fn visit_some<D: Deserializer<'de>>(self, deserializer: D) -> Result<Self::Value, D::Error> {
Ok(Some(serde_bytes::deserialize(deserializer)?))
}
}
deserializer.deserialize_option(VisitorImpl(PhantomData, PhantomData))
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct VerifyAttestationEvidenceRequest {
#[serde(with = "serde_bytes")]
pub isv_enclave_quote: Vec<u8>,
#[serde(skip_serializing_if = "Option::is_none", with = "self::serde_option_bytes")]
pub pse_manifest: Option<Vec<u8>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nonce: Option<String>, }
#[repr(C)]
pub struct EnclaveQuoteBody {
pub version: u16,
pub signature_type: u16,
pub gid: [u8; 4],
pub isvsvn_qe: u16,
pub isvsvn_pce: u16,
pub _reserved0: [u8; 4],
pub basename: [u8; 32],
pub cpusvn: [u8; 16],
pub miscselect: Miscselect,
pub _reserved1: [u8; 28],
pub attributes: Attributes,
pub mrenclave: [u8; 32],
pub _reserved2: [u8; 32],
pub mrsigner: [u8; 32],
pub _reserved3: [u8; 96],
pub isvprodid: u16,
pub isvsvn: u16,
pub _reserved4: [u8; 60],
pub reportdata: [u8; 64],
}
pub const ENCLAVE_QUOTE_BODY_LEN: usize = 432;
impl EnclaveQuoteBody {
pub fn try_copy_from(src: &[u8]) -> Option<Self> {
if src.len() == ENCLAVE_QUOTE_BODY_LEN {
unsafe {
Some(::std::ptr::read_unaligned(src.as_ptr() as *const EnclaveQuoteBody))
}
} else {
None
}
}
unsafe fn _check_size(b: Self) -> [u8; ENCLAVE_QUOTE_BODY_LEN] {
::std::mem::transmute(b)
}
pub fn get_report(self) -> Report {
let EnclaveQuoteBody{cpusvn, miscselect, _reserved1, attributes, mrenclave, _reserved2, mrsigner, _reserved3, isvprodid, isvsvn, _reserved4, reportdata, ..} = self;
Report {
cpusvn,
miscselect,
_reserved1,
attributes,
mrenclave,
_reserved2,
mrsigner,
_reserved3,
isvprodid,
isvsvn,
_reserved4,
reportdata,
keyid: [0; 32],
mac: [0; 16],
}
}
}
fn two() -> u64 {
2
}
fn less_than_three(&v: &u64) -> bool {
v < 3
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct IasAdvisoryId(String);
impl From<&str> for IasAdvisoryId {
fn from(adv: &str) -> Self {
IasAdvisoryId::new(adv)
}
}
impl IasAdvisoryId {
pub fn new(adv: &str) -> Self {
IasAdvisoryId(adv.to_uppercase())
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
pub trait VerificationType {}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct VerifiedSig {}
impl VerificationType for VerifiedSig {}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum Unverified {}
impl VerificationType for Unverified {}
trait SafeToDeserializeInto {}
impl SafeToDeserializeInto for Unverified {}
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
#[serde(bound(deserialize = "V: SafeToDeserializeInto"))]
#[serde(rename_all = "camelCase")]
pub struct VerifyAttestationEvidenceResponse<V: VerificationType = VerifiedSig> {
id: String,
timestamp: String,
#[serde(default = "two", skip_serializing_if = "less_than_three")]
version: u64,
isv_enclave_quote_status: QuoteStatus,
#[serde(with = "serde_bytes")]
isv_enclave_quote_body: Vec<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
revocation_reason: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pse_manifest_status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pse_manifest_hash: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
platform_info_blob: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
nonce: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", with = "self::serde_option_bytes")]
epid_pseudonym: Option<Vec<u8>>,
#[serde(skip_serializing_if = "Option::is_none", rename = "advisoryURL")]
advisory_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", rename = "advisoryIDs")]
advisory_ids: Option<Vec<IasAdvisoryId>>,
#[serde(skip)]
type_: PhantomData<V>
}
#[cfg(feature = "client")]
impl VerifyAttestationEvidenceResponse<Unverified> {
pub(crate) fn advisory_url(&self) -> &Option<String> {
&self.advisory_url
}
pub(crate) fn advisory_ids(&self) -> &Option<Vec<IasAdvisoryId>> {
&self.advisory_ids
}
}
impl VerifyAttestationEvidenceResponse {
#[cfg(feature = "client")]
pub fn from_raw_report<C: Crypto>(raw_report: &[u8], report_sig: &[u8], cert_chain: &Vec<DerSequence>, ca_certificates: &[&[u8]]) -> Result<Self, Error> {
let mut deser = serde_json::Deserializer::from_slice(&raw_report);
let deser = serde_bytes_repr::ByteFmtDeserializer::new_base64(&mut deser, base64::Config::new(base64::CharacterSet::Standard, true));
let resp: VerifyAttestationEvidenceResponse<Unverified> = VerifyAttestationEvidenceResponse::deserialize(deser)
.map_err(|e| Error { error_kind: ErrorKind::ReportJsonParse, cause: Some(Box::new(e)) })?;
verifier::verify_raw_report::<C>(raw_report, report_sig, cert_chain, ca_certificates)?;
let VerifyAttestationEvidenceResponse::<Unverified> {
id,
timestamp,
version,
isv_enclave_quote_status,
isv_enclave_quote_body,
revocation_reason,
pse_manifest_status,
pse_manifest_hash,
platform_info_blob,
nonce,
epid_pseudonym,
advisory_url,
advisory_ids,
type_: _,
} = resp;
Ok(VerifyAttestationEvidenceResponse{
id,
timestamp,
version,
isv_enclave_quote_status,
isv_enclave_quote_body,
revocation_reason,
pse_manifest_status,
pse_manifest_hash,
platform_info_blob,
nonce,
epid_pseudonym,
advisory_url,
advisory_ids,
type_: PhantomData,
})
}
}
impl VerifyAttestationEvidenceResponse {
pub fn id(&self) -> &String {
&self.id
}
pub fn version(&self) -> u64 {
self.version
}
pub fn isv_enclave_quote_status(&self) -> QuoteStatus {
self.isv_enclave_quote_status
}
pub fn isv_enclave_quote_body(&self) -> EnclaveQuoteBody {
EnclaveQuoteBody::try_copy_from(&self.isv_enclave_quote_body)
.expect("Validated at VerifyAttestationEvidenceResponse verification time")
}
pub fn revocation_reason(&self) -> Option<u32> {
self.revocation_reason
}
pub fn pse_manifest_status(&self) -> &Option<String> {
&self.pse_manifest_status
}
pub fn pse_manifest_hash(&self) -> &Option<String> {
&self.pse_manifest_hash
}
pub fn platform_info_blob(&self) -> &Option<String> {
&self.platform_info_blob
}
pub fn nonce(&self) -> &Option<String> {
&self.nonce
}
pub fn epid_pseudonym(&self) -> &Option<Vec<u8>> {
&self.epid_pseudonym
}
pub fn advisory_url(&self) -> &Option<String> {
&self.advisory_url
}
pub fn advisory_ids(&self) -> &Option<Vec<IasAdvisoryId>> {
&self.advisory_ids
}
}
#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum QuoteStatus {
Ok,
SignatureInvalid,
GroupRevoked,
SignatureRevoked,
KeyRevoked,
SigRlVersionMismatch,
GroupOutOfDate,
ConfigurationNeeded,
SwHardeningNeeded,
ConfigurationAndSwHardeningNeeded,
}
macro_rules! pif_v2_bitflags {
($($k:ident = $v:expr);* $(;)*) => (
bitflags! {
pub struct PlatformInfoFlagsV2: u64 {
$(const $k = $v;)*
}
}
static PIF_V2_NAMES: &'static [(PlatformInfoFlagsV2, &'static str)] = &[
$((PlatformInfoFlagsV2::$k, stringify!($k)),)*
];
)
}
pif_v2_bitflags! {
QE_EPID_GROUP_REVOKED = 0x01_0000_0000;
PERF_REKEY_FOR_QE_EPID_GROUP_AVAILABLE = 0x02_0000_0000;
QE_EPID_GROUP_OUT_OF_DATE = 0x04_0000_0000;
QUOTE_CPUSVN_OUT_OF_DATE = 0x00_0001_0000;
QUOTE_ISVSVN_QE_OUT_OF_DATE = 0x00_0002_0000;
QUOTE_ISVSVN_PCE_OUT_OF_DATE = 0x00_0004_0000;
PLATFORM_CONFIGURATION_NEEDED = 0x00_0008_0000;
PSE_ISVSVN_OUT_OF_DATE = 0x00_0000_0001;
EPID_GROUP_ID_BY_PS_HW_GID_REVOKED = 0x00_0000_0002;
SVN_FROM_PS_HW_SEC_INFO_OUT_OF_DATE = 0x00_0000_0004;
SIGRL_VER_FROM_PS_HW_SIG_RLVER_OUT_OF_DATE = 0x00_0000_0008;
PRIVRL_VER_FROM_PS_HW_PRV_KEY_RLVER_OUT_OF_DATE = 0x00_0000_0010;
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum PlatformStatus {
V2(PlatformInfoFlagsV2)
}
impl fmt::Display for PlatformStatus {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let &PlatformStatus::V2(mut flags) = self;
for &(val, name) in PIF_V2_NAMES {
if flags.contains(val) {
flags.remove(val);
write!(f, "{}", name)?;
if !flags.is_empty() {
write!(f, ", ")?;
}
}
}
Ok(())
}
}
impl str::FromStr for PlatformStatus {
type Err = ();
fn from_str(s: &str) -> Result<Self, ()> {
fn parse_hex(s: &str) -> Option<Vec<u8>> {
if !s.is_ascii() || (s.len() % 2) == 1 {
return None
}
s.as_bytes().chunks(2).map(|v| u8::from_str_radix(str::from_utf8(v).unwrap(), 16).ok() ).collect()
}
const PLATFORM_INFO_BLOB_TYPE: u8 = 21;
const PLATFORM_INFO_BLOB_VERSION: u8 = 2;
const PLATFORM_INFO_BLOB_V2_LEN: u16 = 101;
let pinfo = parse_hex(s).ok_or_else(|| warn!("Platform info blob is not properly hex encoded") )?;
let pinfo = &mut &pinfo[..];
let type_ = pinfo.read_u8().map_err(|_|())?;
let version = pinfo.read_u8().map_err(|_|())?;
let len = pinfo.read_u16::<BigEndian>().map_err(|_|())?;
if (type_, version, len) != (PLATFORM_INFO_BLOB_TYPE, PLATFORM_INFO_BLOB_VERSION, PLATFORM_INFO_BLOB_V2_LEN) {
warn!("Platform info blob TLV mismatch");
return Err(())
}
let status = pinfo.read_uint::<BigEndian>(5).map_err(|_|())?;
if let Some(bits) = PlatformInfoFlagsV2::from_bits(status) {
Ok(PlatformStatus::V2(bits))
} else {
warn!("Platform info blob unexpected status bits set: {:010x}", status);
Err(())
}
}
}
#[test]
fn parse_platform_info_blob() {
let blob = "1502006504000100000707020401010000000000000000000005000004000000020000000000000AC888E762232B197B0114CFBFF28163B0D5EF1501399EFE6FF0A7F0CAD93E3A50AD744BA39C8A44FB91F17F5806687202BE0AE5459CD5613588A7B7539E003ABD48";
assert_eq!(blob.parse::<PlatformStatus>().unwrap(), PlatformStatus::V2(PlatformInfoFlagsV2::QE_EPID_GROUP_OUT_OF_DATE | PlatformInfoFlagsV2::QUOTE_CPUSVN_OUT_OF_DATE));
}