#[allow(unused_imports)]
use crate::nostd_prelude::*;
#[cfg(feature = "std")]
use std::time::{SystemTime, UNIX_EPOCH};
use crate::cbor;
use crate::error::ValidationError;
use crate::types::comid::ComidTag;
use crate::types::corim::{ConciseTagChoice, ConciseTlTag, CorimMap};
use crate::types::coswid::ConciseSwidTag;
use crate::types::environment::EnvironmentMap;
use crate::types::measurement::{Digest, MeasurementMap, SvnChoice};
use crate::types::tags::TAG_CORIM;
use crate::types::triples::{
ConditionalEndorsementSeriesTriple, ConditionalSeriesRecord, ReferenceTriple,
};
use crate::Validate;
pub const MAX_PAYLOAD_SIZE: usize = 16 * 1024 * 1024;
#[derive(Clone, Debug)]
pub struct ValidatedCorim {
pub corim: CorimMap,
pub comids: Vec<ComidTag>,
pub cotls: Vec<ConciseTlTag>,
pub coswids: Vec<ConciseSwidTag>,
pub coswid_opaque_count: usize,
}
#[cfg(feature = "std")]
pub fn decode_and_validate(bytes: &[u8]) -> Result<(CorimMap, Vec<ComidTag>), ValidationError> {
let secs = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|e| ValidationError::Clock(e.to_string()))?
.as_secs();
let now = i64::try_from(secs)
.map_err(|_| ValidationError::Clock("system clock beyond i64 range".into()))?;
decode_and_validate_at(bytes, now)
}
pub fn decode_and_validate_at(
bytes: &[u8],
now_epoch_secs: i64,
) -> Result<(CorimMap, Vec<ComidTag>), ValidationError> {
let validated = decode_and_validate_full_impl(bytes, now_epoch_secs)?;
Ok((validated.corim, validated.comids))
}
fn validate_comid(comid: &ComidTag) -> Result<(), ValidationError> {
comid.valid().map_err(ValidationError::Invalid)
}
fn validate_cotl(cotl: &ConciseTlTag, now_epoch_secs: i64) -> Result<(), ValidationError> {
if cotl.tags_list.is_empty() {
return Err(ValidationError::EmptyTagsList);
}
if let Some(nb) = cotl.tl_validity.not_before {
if now_epoch_secs < nb.epoch_secs() {
return Err(ValidationError::NotYetValid);
}
}
if cotl.tl_validity.not_after.epoch_secs() < now_epoch_secs {
return Err(ValidationError::Expired);
}
Ok(())
}
#[cfg(feature = "std")]
pub fn decode_and_validate_full(bytes: &[u8]) -> Result<ValidatedCorim, ValidationError> {
let secs = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|e| ValidationError::Clock(e.to_string()))?
.as_secs();
let now = i64::try_from(secs)
.map_err(|_| ValidationError::Clock("system clock beyond i64 range".into()))?;
decode_and_validate_full_at(bytes, now)
}
pub fn decode_and_validate_full_at(
bytes: &[u8],
now_epoch_secs: i64,
) -> Result<ValidatedCorim, ValidationError> {
decode_and_validate_full_impl(bytes, now_epoch_secs)
}
fn decode_and_validate_full_impl(
bytes: &[u8],
now_epoch_secs: i64,
) -> Result<ValidatedCorim, ValidationError> {
if bytes.len() > MAX_PAYLOAD_SIZE {
return Err(ValidationError::PayloadTooLarge {
size: bytes.len(),
max: MAX_PAYLOAD_SIZE,
});
}
let peeled = crate::compat::peel_tcg_wrappers(bytes).map_err(ValidationError::Decode)?;
let bytes = peeled.as_bytes();
let wrapped = crate::compat::wrap_bare_corim_map(bytes);
let bytes = wrapped.as_bytes();
let tagged: cbor::value::Tagged<CorimMap> =
cbor::decode(bytes).map_err(ValidationError::Decode)?;
if tagged.tag != TAG_CORIM {
return Err(ValidationError::Decode(
crate::error::DecodeError::UnexpectedTag {
expected: TAG_CORIM,
found: tagged.tag,
},
));
}
let corim = tagged.value;
if let Some(ref validity) = corim.rim_validity {
if let Some(nb) = validity.not_before {
if now_epoch_secs < nb.epoch_secs() {
return Err(ValidationError::NotYetValid);
}
}
if validity.not_after.epoch_secs() < now_epoch_secs {
return Err(ValidationError::Expired);
}
}
let mut comids = Vec::new();
let mut cotls = Vec::new();
let mut coswids = Vec::new();
let mut coswid_opaque_count = 0usize;
for tag in &corim.tags {
match tag {
ConciseTagChoice::Comid(comid_bytes) => {
let comid: ComidTag = cbor::decode(comid_bytes).map_err(ValidationError::Decode)?;
validate_comid(&comid)?;
comids.push(comid);
}
ConciseTagChoice::Cotl(cotl_bytes) => {
let cotl: ConciseTlTag =
cbor::decode(cotl_bytes).map_err(ValidationError::Decode)?;
validate_cotl(&cotl, now_epoch_secs)?;
cotls.push(cotl);
}
ConciseTagChoice::Coswid(coswid_bytes) => {
match cbor::decode::<ConciseSwidTag>(coswid_bytes) {
Ok(coswid) => {
coswid.valid().map_err(ValidationError::Invalid)?;
coswids.push(coswid);
}
Err(_) => coswid_opaque_count += 1,
}
}
ConciseTagChoice::BareBstr(bytes) => {
let comid = crate::compat::decode_comid_from_tcg_bstr(bytes)
.map_err(ValidationError::Decode)?;
validate_comid(&comid)?;
comids.push(comid);
}
_ => {
}
}
}
if comids.is_empty() {
return Err(ValidationError::NoComidTags);
}
Ok(ValidatedCorim {
corim,
comids,
cotls,
coswids,
coswid_opaque_count,
})
}
#[derive(Clone, Debug)]
pub struct CorroboratedClaim {
pub environment: EnvironmentMap,
pub measurements: Vec<MeasurementMap>,
}
pub fn match_reference_values(
ref_triples: &[ReferenceTriple],
evidence: &[EvidenceClaim],
) -> Vec<CorroboratedClaim> {
let mut corroborated = Vec::new();
for triple in ref_triples {
for ev in evidence {
if !environment_matches(triple.environment(), &ev.environment) {
continue;
}
let mut matched_measurements = Vec::new();
for ref_meas in triple.measurements() {
if measurement_matches(ref_meas, &ev.measurements) {
matched_measurements.push(ref_meas.clone());
}
}
if !matched_measurements.is_empty() {
corroborated.push(CorroboratedClaim {
environment: triple.environment().clone(),
measurements: matched_measurements,
});
}
}
}
corroborated
}
#[derive(Clone, Debug)]
pub struct EvidenceClaim {
pub environment: EnvironmentMap,
pub measurements: Vec<MeasurementMap>,
}
#[derive(Clone, Debug)]
pub struct EndorsedClaim {
pub environment: EnvironmentMap,
pub endorsements: Vec<MeasurementMap>,
}
pub fn apply_endorsement_series(
ces_triples: &[ConditionalEndorsementSeriesTriple],
evidence: &[EvidenceClaim],
) -> Result<Vec<EndorsedClaim>, ValidationError> {
let mut endorsed = Vec::new();
for triple in ces_triples {
let condition = triple.condition();
let matching_evidence: Vec<_> = evidence
.iter()
.filter(|ev| environment_matches(&condition.environment, &ev.environment))
.collect();
if matching_evidence.is_empty() {
continue;
}
validate_series_mkeys(triple.series())?;
for ev in &matching_evidence {
if let Some(addition) = find_matching_series(triple.series(), &ev.measurements) {
endorsed.push(EndorsedClaim {
environment: condition.environment.clone(),
endorsements: addition,
});
}
}
}
Ok(endorsed)
}
fn validate_series_mkeys(series: &[ConditionalSeriesRecord]) -> Result<(), ValidationError> {
if series.len() <= 1 {
return Ok(());
}
let collect_mkeys = |record: &ConditionalSeriesRecord| -> Vec<String> {
let mut keys: Vec<String> = record
.selection()
.iter()
.map(|m| format!("{:?}", m.mkey))
.collect();
keys.sort();
keys
};
let first_mkeys = collect_mkeys(&series[0]);
for record in &series[1..] {
if collect_mkeys(record) != first_mkeys {
return Err(ValidationError::InconsistentMkeys);
}
}
Ok(())
}
fn find_matching_series(
series: &[ConditionalSeriesRecord],
evidence_measurements: &[MeasurementMap],
) -> Option<Vec<MeasurementMap>> {
for record in series {
let all_match = record
.selection()
.iter()
.all(|sel| measurement_matches(sel, evidence_measurements));
if all_match {
return Some(record.addition().to_vec());
}
}
None
}
pub fn svn_matches(reference: &SvnChoice, evidence_svn: u64) -> bool {
match reference {
SvnChoice::ExactValue(n) => evidence_svn == *n,
SvnChoice::MinValue(n) => evidence_svn >= *n,
}
}
fn environment_matches(condition: &EnvironmentMap, target: &EnvironmentMap) -> bool {
if let Some(ref cond_class) = condition.class {
match &target.class {
None => return false,
Some(tgt_class) => {
if !class_matches(cond_class, tgt_class) {
return false;
}
}
}
}
if condition.instance.is_some() && condition.instance != target.instance {
return false;
}
if condition.group.is_some() && condition.group != target.group {
return false;
}
true
}
fn class_matches(
condition: &crate::types::environment::ClassMap,
target: &crate::types::environment::ClassMap,
) -> bool {
if condition.class_id.is_some() && condition.class_id != target.class_id {
return false;
}
if condition.vendor.is_some() && condition.vendor != target.vendor {
return false;
}
if condition.model.is_some() && condition.model != target.model {
return false;
}
if condition.layer.is_some() && condition.layer != target.layer {
return false;
}
if condition.index.is_some() && condition.index != target.index {
return false;
}
true
}
fn measurement_matches(reference: &MeasurementMap, evidence: &[MeasurementMap]) -> bool {
for ev_meas in evidence {
if let Some(ref ref_mkey) = reference.mkey {
match &ev_meas.mkey {
Some(ev_mkey) if ev_mkey == ref_mkey => {}
_ => continue,
}
}
if let Some(ref ref_digests) = reference.mval.digests {
if let Some(ref ev_digests) = ev_meas.mval.digests {
if !digests_match(ref_digests, ev_digests) {
continue;
}
} else {
continue;
}
}
if let Some(ref ref_svn) = reference.mval.svn {
if let Some(ref ev_svn) = ev_meas.mval.svn {
let ev_val = match ev_svn {
SvnChoice::ExactValue(n) | SvnChoice::MinValue(n) => *n,
};
if !svn_matches(ref_svn, ev_val) {
continue;
}
} else {
continue;
}
}
if reference.mval.version.is_some() && reference.mval.version != ev_meas.mval.version {
continue;
}
if reference.mval.flags.is_some() && reference.mval.flags != ev_meas.mval.flags {
continue;
}
if reference.mval.raw_value.is_some() && reference.mval.raw_value != ev_meas.mval.raw_value
{
continue;
}
if reference.mval.mac_addr.is_some() && reference.mval.mac_addr != ev_meas.mval.mac_addr {
continue;
}
if reference.mval.ip_addr.is_some() && reference.mval.ip_addr != ev_meas.mval.ip_addr {
continue;
}
if reference.mval.serial_number.is_some()
&& reference.mval.serial_number != ev_meas.mval.serial_number
{
continue;
}
if reference.mval.ueid.is_some() && reference.mval.ueid != ev_meas.mval.ueid {
continue;
}
if reference.mval.uuid.is_some() && reference.mval.uuid != ev_meas.mval.uuid {
continue;
}
if reference.mval.name.is_some() && reference.mval.name != ev_meas.mval.name {
continue;
}
if reference.mval.integrity_registers.is_some()
&& reference.mval.integrity_registers != ev_meas.mval.integrity_registers
{
continue;
}
if reference.mval.int_range.is_some() && reference.mval.int_range != ev_meas.mval.int_range
{
continue;
}
return true;
}
false
}
fn digests_match(reference: &[Digest], evidence: &[Digest]) -> bool {
let mut has_common = false;
for ref_d in reference {
for ev_d in evidence {
if ref_d.alg() == ev_d.alg() {
has_common = true;
if ref_d.value() != ev_d.value() {
return false;
}
}
}
}
has_common
}
#[derive(Clone, Debug, PartialEq)]
pub enum ClaimType {
Evidence,
ReferenceValues,
Endorsement,
}
#[derive(Clone, Debug)]
pub struct EnvironmentClaimTuple {
pub environment: EnvironmentMap,
pub measurements: Vec<MeasurementMap>,
pub claim_type: ClaimType,
}
#[derive(Clone, Debug, Default)]
pub struct AppraisalContext {
pub entries: Vec<EnvironmentClaimTuple>,
}
impl AppraisalContext {
pub fn new() -> Self {
Self::default()
}
pub fn add_evidence(&mut self, claims: Vec<EvidenceClaim>) {
for claim in claims {
self.entries.push(EnvironmentClaimTuple {
environment: claim.environment,
measurements: claim.measurements,
claim_type: ClaimType::Evidence,
});
}
}
pub fn apply_reference_values(
&mut self,
ref_triples: &[ReferenceTriple],
) -> Vec<CorroboratedClaim> {
let evidence: Vec<EvidenceClaim> = self
.entries
.iter()
.filter(|e| e.claim_type == ClaimType::Evidence)
.map(|e| EvidenceClaim {
environment: e.environment.clone(),
measurements: e.measurements.clone(),
})
.collect();
let corroborated = match_reference_values(ref_triples, &evidence);
for claim in &corroborated {
self.entries.push(EnvironmentClaimTuple {
environment: claim.environment.clone(),
measurements: claim.measurements.clone(),
claim_type: ClaimType::ReferenceValues,
});
}
corroborated
}
pub fn apply_conditional_endorsements(
&mut self,
ces_triples: &[ConditionalEndorsementSeriesTriple],
) -> Result<Vec<EndorsedClaim>, ValidationError> {
let evidence: Vec<EvidenceClaim> = self
.entries
.iter()
.map(|e| EvidenceClaim {
environment: e.environment.clone(),
measurements: e.measurements.clone(),
})
.collect();
let endorsed = apply_endorsement_series(ces_triples, &evidence)?;
for claim in &endorsed {
self.entries.push(EnvironmentClaimTuple {
environment: claim.environment.clone(),
measurements: claim.endorsements.clone(),
claim_type: ClaimType::Endorsement,
});
}
Ok(endorsed)
}
}