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;
mod tcbdate;
pub use expression::{
display_expression, Expression, ExpressionDecodeError, Numeric, NumericOp, SetOp,
TAG_INTEL_EXPRESSION, TAG_INTEL_SET_DIGEST_EXPRESSION, TAG_INTEL_SET_TSTR_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_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_CRYPTOKEYS: i64 = -91;
pub const MVAL_TEE_PLATFORM_INSTANCE_ID: i64 = -101;
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(*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_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_CRYPTOKEYS => "tee.cryptokeys",
MVAL_TEE_PLATFORM_INSTANCE_ID => "tee.platform-instance-id",
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 Expression::is_intel_expression_tag(*tag) => {
match Expression::from_tag(v) {
Ok(expr) => display_expression(&expr),
Err(_) => format!("#6.{}(…)", tag),
}
}
Value::Tag(1001, _) => "etime(…)".to_string(),
Value::Tag(1002, _) => "duration(…)".to_string(),
Value::Tag(1003, _) => "period(…)".to_string(),
Value::Tag(0, inner) => match inner.as_ref() {
Value::Text(t) => format!("tdate(\"{}\")", t),
_ => "#6.0(…)".to_string(),
},
Value::Tag(1, inner) => match inner.as_ref() {
Value::Integer(n) => format!("time({})", n),
Value::Float(f) => format!("time({})", f),
_ => "#6.1(…)".to_string(),
},
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(-77), None); assert_eq!(intel_mval_name(-90), None); assert_eq!(intel_mval_name(-100), None);
assert_eq!(intel_mval_name(-126), None);
}
#[test]
fn platform_instance_id_is_recognised() {
assert_eq!(intel_mval_name(-101), Some("tee.platform-instance-id"));
}
#[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_renders_v07_set_tstr_expression() {
let p = IntelProfile::new();
let expr = Value::Tag(
TAG_INTEL_SET_TSTR_EXPRESSION,
Box::new(Value::Array(vec![
Value::Integer(6), Value::Array(vec![
Value::Text("UpToDate".into()),
Value::Text("Hardening".into()),
]),
])),
);
let label = p.diagnose_mval_entry(MVAL_TEE_TCBSTATUS, &expr);
assert_eq!(label.as_deref(), Some("tee.tcbstatus = member (2 strings)"));
}
#[test]
fn diagnose_renders_int_range_expression() {
let p = IntelProfile::new();
let expr = Value::Tag(
crate::types::tags::TAG_INT_RANGE,
Box::new(Value::Array(vec![Value::Integer(0), Value::Integer(15)])),
);
let label = p.diagnose_mval_entry(MVAL_TEE_ISVSVN, &expr);
assert_eq!(label.as_deref(), Some("tee.isvsvn = range [0..15]"));
}
#[test]
fn diagnose_renders_min_svn_expression() {
let p = IntelProfile::new();
let expr = Value::Tag(crate::types::tags::TAG_MIN_SVN, Box::new(Value::Integer(7)));
let label = p.diagnose_mval_entry(MVAL_TEE_ISVSVN, &expr);
assert_eq!(label.as_deref(), Some("tee.isvsvn = min-svn 7"));
}
#[test]
fn diagnose_renders_masked_raw_value_expression() {
let p = IntelProfile::new();
let expr = Value::Tag(
crate::types::tags::TAG_MASKED_RAW_VALUE,
Box::new(Value::Array(vec![
Value::Bytes(vec![0xF0, 0x00, 0x00, 0x00]),
Value::Bytes(vec![0xF0, 0xFF, 0xFF, 0xFF]),
])),
);
let label = p.diagnose_mval_entry(MVAL_TEE_ATTRIBUTES, &expr);
assert_eq!(
label.as_deref(),
Some("tee.attributes = masked-bstr <4-byte value, 4-byte mask>")
);
}
#[test]
fn diagnose_labels_rfc9581_time_tags() {
let p = IntelProfile::new();
let etime = Value::Tag(1001, Box::new(Value::Map(vec![])));
let label = p.diagnose_mval_entry(MVAL_TEE_TCBDATE, &etime);
assert_eq!(label.as_deref(), Some("tee.tcbdate = etime(…)"));
let period = Value::Tag(1003, Box::new(Value::Array(vec![])));
let label = p.diagnose_mval_entry(MVAL_TEE_TCBDATE, &period);
assert_eq!(label.as_deref(), Some("tee.tcbdate = period(…)"));
}
#[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)"));
}
}