use crate::cbor::value::Value;
use crate::nostd_prelude::*;
use crate::profile::{MatchContext, Profile};
use crate::types::corim::ProfileChoice;
use crate::types::measurement::MeasurementMap;
mod eval;
pub mod expression;
pub use expression::{
display_expression, Expression, ExpressionDecodeError, Numeric, NumericOp, SetOfSetOp, SetOp,
TAG_INTEL_EXPRESSION,
};
pub const INTEL_PROFILE_OID_DER: &[u8] =
&[0x60, 0x86, 0x48, 0x01, 0x86, 0xF8, 0x4D, 0x01, 0x10, 0x01];
pub const MVAL_TEE_VENDOR: i64 = -70;
pub const MVAL_TEE_MODEL: i64 = -71;
pub const MVAL_TEE_TCBDATE: i64 = -72;
pub const MVAL_TEE_ISVSVN: i64 = -73;
pub const MVAL_TEE_INSTANCE_ID: i64 = -77;
pub const MVAL_TEE_PCEID: i64 = -80;
pub const MVAL_TEE_MISCSELECT: i64 = -81;
pub const MVAL_TEE_ATTRIBUTES: i64 = -82;
pub const MVAL_TEE_MRTEE: i64 = -83;
pub const MVAL_TEE_MRSIGNER: i64 = -84;
pub const MVAL_TEE_ISVPRODID: i64 = -85;
pub const MVAL_TEE_TCB_EVAL_NUM: i64 = -86;
pub const MVAL_TEE_TCBSTATUS: i64 = -88;
pub const MVAL_TEE_ADVISORY_IDS: i64 = -89;
pub const MVAL_TEE_EPOCH: i64 = -90;
pub const MVAL_TEE_CRYPTOKEYS: i64 = -91;
pub const MVAL_TEE_TCB_COMP_SVN: i64 = -125;
#[derive(Debug)]
pub struct IntelProfile {
id: ProfileChoice,
}
impl IntelProfile {
pub fn new() -> Self {
Self {
id: ProfileChoice::Oid(INTEL_PROFILE_OID_DER.to_vec()),
}
}
}
impl Default for IntelProfile {
fn default() -> Self {
Self::new()
}
}
impl Profile for IntelProfile {
fn identifier(&self) -> &ProfileChoice {
&self.id
}
fn match_measurement(
&self,
reference: &MeasurementMap,
evidence: &MeasurementMap,
ctx: &MatchContext,
) -> Option<bool> {
let mut verdicts: Vec<eval::Verdict> = Vec::new();
for (key, ref_val) in reference.mval.extra_entries.iter() {
if intel_mval_name(*key).is_none() {
continue;
}
match evidence.mval.extra_entries.get(key) {
Some(ev_val) => verdicts.push(eval::evaluate_one_key(ref_val, ev_val, ctx)),
None => return Some(false),
}
}
match eval::combine(&verdicts) {
Some(true) => Some(crate::validate::core_fields_match(reference, evidence)),
other => other, }
}
fn diagnose_mval_entry(&self, key: i64, value: &Value) -> Option<String> {
let name = intel_mval_name(key)?;
Some(format!("{} = {}", name, value_summary(value)))
}
}
pub fn intel_mval_name(key: i64) -> Option<&'static str> {
Some(match key {
MVAL_TEE_VENDOR => "tee.vendor",
MVAL_TEE_MODEL => "tee.model",
MVAL_TEE_TCBDATE => "tee.tcbdate",
MVAL_TEE_ISVSVN => "tee.isvsvn",
MVAL_TEE_INSTANCE_ID => "tee.instance-id",
MVAL_TEE_PCEID => "tee.pceid",
MVAL_TEE_MISCSELECT => "tee.miscselect",
MVAL_TEE_ATTRIBUTES => "tee.attributes",
MVAL_TEE_MRTEE => "tee.mrtee",
MVAL_TEE_MRSIGNER => "tee.mrsigner",
MVAL_TEE_ISVPRODID => "tee.isvprodid",
MVAL_TEE_TCB_EVAL_NUM => "tee.tcb-eval-num",
MVAL_TEE_TCBSTATUS => "tee.tcbstatus",
MVAL_TEE_ADVISORY_IDS => "tee.advisory-ids",
MVAL_TEE_EPOCH => "tee.epoch",
MVAL_TEE_CRYPTOKEYS => "tee.cryptokeys",
MVAL_TEE_TCB_COMP_SVN => "tee.tcb-comp-svn",
_ => return None,
})
}
fn value_summary(v: &Value) -> String {
match v {
Value::Integer(n) => format!("{}", n),
Value::Text(t) => {
if t.len() <= 48 {
format!("\"{}\"", t)
} else {
format!("\"{}…\" ({} chars)", &t[..47], t.len())
}
}
Value::Bytes(b) => format!("<{}-byte bstr>", b.len()),
Value::Array(a) => format!("array[{}]", a.len()),
Value::Map(m) => format!("map({} entries)", m.len()),
Value::Tag(tag, _) if *tag == TAG_INTEL_EXPRESSION => match Expression::from_tag(v) {
Ok(expr) => display_expression(&expr),
Err(_) => format!("#6.{}(…)", tag),
},
Value::Tag(tag, _) => format!("#6.{}(…)", tag),
Value::Bool(b) => {
if *b {
"true".to_string()
} else {
"false".to_string()
}
}
Value::Null => "null".to_string(),
Value::Float(f) => format!("{}", f),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn oid_round_trips_through_profile_choice() {
let p = IntelProfile::new();
match p.identifier() {
ProfileChoice::Oid(b) => assert_eq!(b.as_slice(), INTEL_PROFILE_OID_DER),
other => panic!("expected Oid, got {:?}", other),
}
}
#[test]
fn known_mval_keys_map_to_names() {
assert_eq!(intel_mval_name(-70), Some("tee.vendor"));
assert_eq!(intel_mval_name(-83), Some("tee.mrtee"));
assert_eq!(intel_mval_name(-125), Some("tee.tcb-comp-svn"));
}
#[test]
fn unknown_mval_keys_return_none() {
assert_eq!(intel_mval_name(-1), None);
assert_eq!(intel_mval_name(0), None);
assert_eq!(intel_mval_name(-100), None);
assert_eq!(intel_mval_name(-126), None);
}
#[test]
fn diagnose_renders_known_keys() {
let p = IntelProfile::new();
let label = p.diagnose_mval_entry(MVAL_TEE_VENDOR, &Value::Text("Intel".to_string()));
assert_eq!(label.as_deref(), Some("tee.vendor = \"Intel\""));
let digest_array = Value::Array(vec![Value::Integer(1), Value::Bytes(vec![0u8; 32])]);
let label = p.diagnose_mval_entry(MVAL_TEE_MRTEE, &digest_array);
assert_eq!(label.as_deref(), Some("tee.mrtee = array[2]"));
let label = p.diagnose_mval_entry(MVAL_TEE_ISVSVN, &Value::Integer(3));
assert_eq!(label.as_deref(), Some("tee.isvsvn = 3"));
}
#[test]
fn diagnose_returns_none_for_unknown_keys() {
let p = IntelProfile::new();
assert_eq!(p.diagnose_mval_entry(-1, &Value::Integer(0)), None);
assert_eq!(p.diagnose_mval_entry(9999, &Value::Null), None);
}
#[test]
fn long_text_is_truncated() {
let p = IntelProfile::new();
let long = "x".repeat(100);
let label = p
.diagnose_mval_entry(MVAL_TEE_VENDOR, &Value::Text(long))
.expect("known key");
assert!(label.contains("…"));
assert!(label.contains("(100 chars)"));
}
}