diff --git a/corim-cli/Cargo.toml b/corim-cli/Cargo.toml index 3233a62..e1d50d1 100644 --- a/corim-cli/Cargo.toml +++ b/corim-cli/Cargo.toml @@ -30,7 +30,7 @@ base64 = "0.22" # `--diagnose` labels vendor-defined mval keys by their spec names # out of the box, and `generate` resolves their mval aliases. Use # `--no-default-features` to disable. -default = ["intel", "azure", "psa"] +default = ["intel", "azure", "psa", "cca"] # Register the Intel CoRIM profile (draft-cds-rats-intel-corim-profile) # in the diagnose registry so `--diagnose` labels Intel tee.* mval keys # by their spec names instead of "extension key ". @@ -41,3 +41,6 @@ azure = ["corim/profile-azure"] # Register the Arm PSA profile so `generate` resolves its `psa-cert-num` # mval alias and `--diagnose` labels it. psa = ["corim/profile-psa"] +# Register the Arm CCA endorsements profile so CCA platform/realm +# measurement keys are recognised as part of the CCA profile. +cca = ["corim/profile-cca"] diff --git a/corim-cli/src/convert.rs b/corim-cli/src/convert.rs index 0c3d84e..53cd527 100644 --- a/corim-cli/src/convert.rs +++ b/corim-cli/src/convert.rs @@ -270,6 +270,10 @@ fn build_registry() -> ProfileRegistry { registry.register(Box::new(corim::profile::azure::AzureProfile::new())); #[cfg(feature = "psa")] registry.register(Box::new(corim::profile::psa::PsaProfile::new())); + #[cfg(feature = "cca")] + registry.register(Box::new(corim::profile::cca::CcaPlatformProfile::new())); + #[cfg(feature = "cca")] + registry.register(Box::new(corim::profile::cca::CcaRealmProfile::new())); registry } diff --git a/corim-cli/src/generate.rs b/corim-cli/src/generate.rs index 33f9371..5731abc 100644 --- a/corim-cli/src/generate.rs +++ b/corim-cli/src/generate.rs @@ -413,5 +413,9 @@ fn build_registry() -> ProfileRegistry { registry.register(Box::new(corim::profile::azure::AzureProfile::new())); #[cfg(feature = "psa")] registry.register(Box::new(corim::profile::psa::PsaProfile::new())); + #[cfg(feature = "cca")] + registry.register(Box::new(corim::profile::cca::CcaPlatformProfile::new())); + #[cfg(feature = "cca")] + registry.register(Box::new(corim::profile::cca::CcaRealmProfile::new())); registry } diff --git a/corim-cli/src/main.rs b/corim-cli/src/main.rs index e44460d..6fc98f8 100644 --- a/corim-cli/src/main.rs +++ b/corim-cli/src/main.rs @@ -27,6 +27,10 @@ fn build_registry() -> corim::profile::ProfileRegistry { registry.register(Box::new(corim::profile::azure::AzureProfile::new())); #[cfg(feature = "psa")] registry.register(Box::new(corim::profile::psa::PsaProfile::new())); + #[cfg(feature = "cca")] + registry.register(Box::new(corim::profile::cca::CcaPlatformProfile::new())); + #[cfg(feature = "cca")] + registry.register(Box::new(corim::profile::cca::CcaRealmProfile::new())); registry } @@ -360,6 +364,11 @@ decoded via compat::decode_comid_from_tcg_bstr", } } + if let Some(profile) = profile_for_render { + validate_profile_reference_triples(profile, &comid_tags, &mut errors); + validate_profile_attest_key_triples(profile, &comid_tags, &mut errors); + } + // Baseline conformance mode: compare the (valid) input against a // known-good baseline and exit on the conformance result. if let Some(baseline_path) = &cli.baseline { @@ -418,6 +427,48 @@ decoded via compat::decode_comid_from_tcg_bstr", } } +fn validate_profile_reference_triples( + profile: &(dyn corim::profile::Profile + Send + Sync), + comids: &[corim::types::comid::ComidTag], + errors: &mut Vec, +) { + let profile_name = display::profile_str(profile.identifier()); + for (comid_idx, comid) in comids.iter().enumerate() { + let Some(reference_triples) = &comid.triples.reference_triples else { + continue; + }; + + for (triple_idx, triple) in reference_triples.iter().enumerate() { + if !profile.reference_triple_valid(triple) { + errors.push(format!( + "comids[{comid_idx}].reference-triples[{triple_idx}]: failed profile-specific validation for {profile_name}" + )); + } + } + } +} + +fn validate_profile_attest_key_triples( + profile: &(dyn corim::profile::Profile + Send + Sync), + comids: &[corim::types::comid::ComidTag], + errors: &mut Vec, +) { + let profile_name = display::profile_str(profile.identifier()); + for (comid_idx, comid) in comids.iter().enumerate() { + let Some(attest_key_triples) = &comid.triples.attest_key_triples else { + continue; + }; + + for (triple_idx, triple) in attest_key_triples.iter().enumerate() { + if !profile.attest_key_triple_valid(triple) { + errors.push(format!( + "comids[{comid_idx}].attest-key-triples[{triple_idx}]: failed profile-specific validation for {profile_name}" + )); + } + } + } +} + /// Information extracted from a signed CoRIM's COSE_Sign1 wrapper. /// /// Mirrors the four elements of the RFC 9052 §4 `COSE_Sign1` array: diff --git a/corim-cli/tests/validate_json_tests.rs b/corim-cli/tests/validate_json_tests.rs index a119fc6..76a5219 100644 --- a/corim-cli/tests/validate_json_tests.rs +++ b/corim-cli/tests/validate_json_tests.rs @@ -7,10 +7,12 @@ use std::process::Command; use corim::builder::{ComidBuilder, CorimBuilder}; -use corim::types::common::{MeasuredElement, TagIdChoice}; +use corim::types::common::{ClassIdChoice, CryptoKey, MeasuredElement, TagIdChoice}; use corim::types::corim::{CorimId, CorimMetaMap, CorimSignerMap}; use corim::types::environment::{ClassMap, EnvironmentMap}; -use corim::types::measurement::{MeasurementMap, MeasurementValuesMap, SvnChoice}; +use corim::types::measurement::{ + Digest, MeasurementMap, MeasurementValuesMap, RawValueChoice, SvnChoice, +}; use corim::types::signed::{CwtClaims, SignedCorimBuilder}; use corim::types::triples::ReferenceTriple; @@ -97,6 +99,107 @@ fn validate_json(bytes: &[u8], ext: &str) -> serde_json::Value { .unwrap_or_else(|e| panic!("output is not valid JSON: {e}\n{stdout}")) } +fn validate_json_status(bytes: &[u8], ext: &str) -> (std::process::ExitStatus, serde_json::Value) { + let path = unique_temp("validate_json_status", ext); + std::fs::write(&path, bytes).unwrap(); + let out = Command::new(bin()) + .args(["validate", "-f", "json", path.to_str().unwrap()]) + .output() + .expect("run validate"); + let _ = std::fs::remove_file(&path); + let stdout = String::from_utf8(out.stdout).expect("utf-8 stdout"); + let parsed = serde_json::from_str(&stdout) + .unwrap_or_else(|e| panic!("output is not valid JSON: {e}\n{stdout}")); + (out.status, parsed) +} + +fn cca_platform_environment() -> EnvironmentMap { + EnvironmentMap { + class: Some(ClassMap { + class_id: Some(ClassIdChoice::Bytes(vec![0x5A; 32])), + ..ClassMap::default() + }), + instance: None, + group: None, + } +} + +fn cca_software_component() -> MeasurementMap { + MeasurementMap { + mkey: Some(MeasuredElement::Text("cca.software-component".into())), + mval: MeasurementValuesMap { + digests: Some(vec![Digest::new_text("sha-256", vec![0x11; 32])]), + cryptokeys: Some(vec![CryptoKey::Bytes(vec![0xAA; 32])]), + ..MeasurementValuesMap::default() + }, + authorized_by: None, + } +} + +fn cca_platform_config() -> MeasurementMap { + MeasurementMap { + mkey: Some(MeasuredElement::Text("cca.platform-config".into())), + mval: MeasurementValuesMap { + raw_value: Some(RawValueChoice::Masked { + value: vec![0xA0, 0x05], + mask: vec![0xF0, 0x00], + }), + ..MeasurementValuesMap::default() + }, + authorized_by: None, + } +} + +fn cca_platform_corim(measurements: Vec) -> Vec { + let comid = ComidBuilder::new(TagIdChoice::Text("cca-platform-comid".into())) + .add_reference_triple(ReferenceTriple::new( + cca_platform_environment(), + measurements, + )) + .build() + .unwrap(); + + CorimBuilder::new(CorimId::Text("cca-platform-corim".into())) + .set_profile(corim::types::corim::ProfileChoice::Uri( + "tag:arm.com,2025:endorsements/cca_platform#1.0.0".into(), + )) + .add_comid_tag(comid) + .unwrap() + .build_bytes() + .unwrap() +} + +fn cca_platform_instance_environment() -> EnvironmentMap { + EnvironmentMap { + instance: Some(corim::types::common::InstanceIdChoice::Ueid( + [&[0x01u8][..], &[0x5A; 32]].concat(), + )), + ..cca_platform_environment() + } +} + +fn cca_platform_corim_with_attest_key(environment: EnvironmentMap, keys: Vec) -> Vec { + let comid = ComidBuilder::new(TagIdChoice::Text("cca-platform-comid".into())) + .add_reference_triple(ReferenceTriple::new( + cca_platform_environment(), + vec![cca_software_component(), cca_platform_config()], + )) + .add_attest_key_triple(corim::types::triples::AttestKeyTriple::new( + environment, keys, None, + )) + .build() + .unwrap(); + + CorimBuilder::new(CorimId::Text("cca-platform-corim".into())) + .set_profile(corim::types::corim::ProfileChoice::Uri( + "tag:arm.com,2025:endorsements/cca_platform#1.0.0".into(), + )) + .add_comid_tag(comid) + .unwrap() + .build_bytes() + .unwrap() +} + #[test] fn signed_corim_json_includes_protected_header_fields() { let signed = make_signed(&sample_unsigned_corim(), false); @@ -151,6 +254,69 @@ fn unsigned_corim_json_has_no_signed_object() { assert_eq!(v["id"], "json-corim"); } +#[test] +fn validate_accepts_cca_platform_profile_reference_triples() { + let bytes = cca_platform_corim(vec![cca_software_component(), cca_platform_config()]); + let v = validate_json(&bytes, "cbor"); + + assert_eq!(v["valid"], true); + assert_eq!( + v["profile"], + "tag:arm.com,2025:endorsements/cca_platform#1.0.0" + ); +} + +#[test] +fn validate_rejects_invalid_cca_platform_profile_reference_triples() { + let bytes = cca_platform_corim(vec![cca_software_component()]); + let (status, v) = validate_json_status(&bytes, "cbor"); + + assert!(!status.success(), "validate unexpectedly succeeded: {v}"); + assert_eq!(v["valid"], false); + assert!( + v["errors"].as_array().unwrap().iter().any(|error| error + .as_str() + .is_some_and(|s| s.contains("failed profile-specific validation") + && s.contains("tag:arm.com,2025:endorsements/cca_platform#1.0.0"))), + "expected profile-specific validation error, got: {v}" + ); +} + +#[test] +fn validate_accepts_cca_platform_profile_attest_key_triple() { + let bytes = cca_platform_corim_with_attest_key( + cca_platform_instance_environment(), + vec![CryptoKey::PkixBase64Key( + "-----BEGIN PUBLIC KEY-----\nMA==\n-----END PUBLIC KEY-----".into(), + )], + ); + let v = validate_json(&bytes, "cbor"); + + assert_eq!(v["valid"], true); +} + +#[test] +fn validate_rejects_invalid_cca_platform_profile_attest_key_triple() { + // §3.1.4 requires exactly one `tagged-pkix-base64-key-type` key; an + // opaque key-identifier bytes value must be rejected. + let bytes = cca_platform_corim_with_attest_key( + cca_platform_instance_environment(), + vec![CryptoKey::Bytes(vec![0xAA; 32])], + ); + let (status, v) = validate_json_status(&bytes, "cbor"); + + assert!(!status.success(), "validate unexpectedly succeeded: {v}"); + assert_eq!(v["valid"], false); + assert!( + v["errors"].as_array().unwrap().iter().any(|error| error + .as_str() + .is_some_and(|s| s.contains("attest-key-triples") + && s.contains("failed profile-specific validation") + && s.contains("tag:arm.com,2025:endorsements/cca_platform#1.0.0"))), + "expected profile-specific validation error, got: {v}" + ); +} + /// Producer-controlled strings reach the report verbatim, so control /// characters must be escaped rather than emitted raw (which would make the /// output unparseable). diff --git a/corim/Cargo.toml b/corim/Cargo.toml index 5663ef0..315d3d7 100644 --- a/corim/Cargo.toml +++ b/corim/Cargo.toml @@ -37,6 +37,10 @@ profile-azure = [] # First-party Arm PSA profile: the `psa-cert-num` (key 100) # measurement-values-map extension from draft-ietf-rats-corim-11. profile-psa = [] +# First-party Arm CCA endorsements profile. +# Implements the draft-ydb-rats-cca-endorsements-04 profile identifiers and +# the CCA platform/realm measurement-key naming conventions. +profile-cca = [] [dependencies] corim-macros.workspace = true diff --git a/corim/src/profile.rs b/corim/src/profile.rs index b15de21..79a628e 100644 --- a/corim/src/profile.rs +++ b/corim/src/profile.rs @@ -30,6 +30,7 @@ //! | `profile-intel` | [`intel`](crate::profile::intel) | `draft-cds-rats-intel-corim-profile-03` | //! | `profile-azure` | `azure` (feature-gated) | Azure `tcbstatus` example extension | //! | `profile-psa` | `psa` (feature-gated) | Arm PSA `psa-cert-num` (draft-corim-11) | +//! | `profile-cca` | `cca` (feature-gated) | Arm CCA endorsements (draft-ydb-rats-cca-endorsements-04) | //! //! Third-party profiles are first-class — the [`Profile`](crate::profile::Profile) trait is //! public and stable, and out-of-tree crates may publish their own @@ -157,6 +158,7 @@ use crate::cbor::value::Value; use crate::types::common::CborTime; use crate::types::corim::ProfileChoice; use crate::types::measurement::MeasurementMap; +use crate::types::triples::{AttestKeyTriple, ReferenceTriple}; /// First-party Intel CoRIM profile (`draft-cds-rats-intel-corim-profile`). /// @@ -185,6 +187,18 @@ pub mod azure; #[cfg_attr(docsrs, doc(cfg(feature = "profile-psa")))] pub mod psa; +/// Minimal Arm CCA endorsements profile support for +/// `draft-ydb-rats-cca-endorsements-04`. +/// +/// The module recognizes CCA Platform / Realm profile URIs and measurement +/// keys, validates CCA-specific measurement shapes and environment subject +/// identifiers, enforces triple-level cardinality and linkage constraints, +/// and adds matching semantics for CCA cryptokeys and masked configuration +/// reference values that the generic matcher deliberately does not handle. +#[cfg(feature = "profile-cca")] +#[cfg_attr(docsrs, doc(cfg(feature = "profile-cca")))] +pub mod cca; + // --------------------------------------------------------------------------- // MatchContext // --------------------------------------------------------------------------- @@ -288,6 +302,50 @@ pub trait Profile { None } + /// Validate profile-specific constraints over a whole reference + /// triple before per-measurement appraisal begins. + /// + /// Use this when the profile has requirements that cannot be checked from + /// one `(reference, evidence)` measurement pair alone — a mandatory + /// measurement that must appear somewhere in the triple, a cardinality + /// constraint across measurements, or a constraint on the triple's + /// [`environment`][crate::types::triples::ReferenceTriple::environment] + /// such as a profile-defined subject identifier that must be present + /// and consistent with the measurements. Return `false` to make the + /// whole reference triple ineligible for profile-aware matching. + /// Profiles without triple-level requirements can use the default + /// implementation. + fn reference_triple_valid(&self, _triple: &ReferenceTriple) -> bool { + true + } + + /// Validate profile-specific constraints over an attestation-key + /// triple. + /// + /// Use this when the profile places stricter requirements on + /// `attest-key-triple-record` than the generic + /// [`AttestKeyTriple::valid`][crate::types::triples::AttestKeyTriple] + /// check (a non-empty key list) — for example a profile-defined subject + /// identifier that must be present on the triple's environment, or a + /// constraint on the number or encoding of the verification keys. + /// Return `false` to reject the triple. Profiles without triple-level + /// requirements can use the default implementation. + fn attest_key_triple_valid(&self, _triple: &AttestKeyTriple) -> bool { + true + } + + /// Validate profile-specific constraints over one evidence claim before + /// it is matched against any reference triple. + /// + /// Use this when evidence produced for a profile must satisfy identity or + /// shape requirements that are stricter than the generic CoRIM environment + /// matching rules. Return `false` to make the evidence claim ineligible for + /// profile-aware matching. Profiles without evidence-level requirements can + /// use the default implementation. + fn evidence_claim_valid(&self, _claim: &crate::validate::EvidenceClaim) -> bool { + true + } + /// Render an `extra_entries` key/value pair for `--diagnose` output. /// /// Called by the diagnose walker when it encounters a profile-defined diff --git a/corim/src/profile/cca/mod.rs b/corim/src/profile/cca/mod.rs new file mode 100644 index 0000000..8dda926 --- /dev/null +++ b/corim/src/profile/cca/mod.rs @@ -0,0 +1,683 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! Minimal Arm CCA endorsements profile support for +//! `draft-ydb-rats-cca-endorsements-04`. +//! +//! The draft defines two specific CoRIM profile URIs: +//! +//! - `tag:arm.com,2025:endorsements/cca_platform#1.0.0` +//! - `tag:arm.com,2025:endorsements/cca_realm#1.0.0` +//! +//! and a set of characteristic `measurement-map` names: +//! +//! - Platform: `cca.software-component`, `cca.platform-config`, +//! `cca.rotpk.CM..`, `cca.rotpk.DM..`, +//! `cca.platform-manufacturing-config` +//! - Realm: `cca.rim`, `cca.rem0`..`cca.rem3`, `cca.rpv` +//! +//! The core crate already knows how to compare most underlying CBOR value +//! shapes (`digests`, `raw-value`, etc.) for these maps. This profile adds +//! CCA-specific checks for fields the generic matcher does not own, including: +//! +//! - identifying the CCA profile URI, +//! - validating the CCA-specific `mkey` names, +//! - enforcing the CCA-specific measurement-map shapes, +//! - matching cryptokeys, ROTPK raw evidence, and masked configuration +//! reference values, +//! - enforcing the triple-level constraints: the environment subject +//! (Platform Implementation ID / Realm RIM) and the measurement +//! cardinality a reference triple must satisfy. + +use crate::nostd_prelude::*; +use crate::profile::{MatchContext, Profile}; +use crate::types::common::{ClassIdChoice, CryptoKey, InstanceIdChoice, MeasuredElement}; +use crate::types::corim::ProfileChoice; +use crate::types::environment::EnvironmentMap; +use crate::types::measurement::{ + Digest, DigestAlg, MeasurementMap, MeasurementValuesMap, RawValueChoice, +}; +use crate::types::triples::{AttestKeyTriple, ReferenceTriple}; +use crate::validate::EvidenceClaim; + +/// Profile URI for CCA Platform endorsements +/// (draft-ydb-rats-cca-endorsements-04 §3.1.1). +pub const CCA_PLATFORM_PROFILE_URI: &str = "tag:arm.com,2025:endorsements/cca_platform#1.0.0"; +/// Profile URI for CCA Realm endorsements +/// (draft-ydb-rats-cca-endorsements-04 §3.2.1). +pub const CCA_REALM_PROFILE_URI: &str = "tag:arm.com,2025:endorsements/cca_realm#1.0.0"; + +/// CCA Platform software-component measurement key +/// (draft-ydb-rats-cca-endorsements-04 §3.1.3.1). +pub const CCA_MKEY_SOFTWARE_COMPONENT: &str = "cca.software-component"; +/// CCA Platform configuration measurement key +/// (draft-ydb-rats-cca-endorsements-04 §3.1.3.2). +pub const CCA_MKEY_PLATFORM_CONFIG: &str = "cca.platform-config"; +/// CCA Platform manufacturing configuration measurement key +/// (draft-ydb-rats-cca-endorsements-04 §3.1.3.4). +pub const CCA_MKEY_PLATFORM_MANUFACTURING_CONFIG: &str = "cca.platform-manufacturing-config"; +/// Prefix for CCA Platform ROTPK measurement keys +/// (draft-ydb-rats-cca-endorsements-04 §3.1.3.3). +pub const CCA_MKEY_ROTPK_PREFIX: &str = "cca.rotpk."; +/// CCA Realm initial measurement key +/// (draft-ydb-rats-cca-endorsements-04 §3.2.3). +pub const CCA_MKEY_RIM: &str = "cca.rim"; +/// CCA Realm extended measurement key for bank 0 +/// (draft-ydb-rats-cca-endorsements-04 §3.2.3). +pub const CCA_MKEY_REM0: &str = "cca.rem0"; +/// CCA Realm extended measurement key for bank 1 +/// (draft-ydb-rats-cca-endorsements-04 §3.2.3). +pub const CCA_MKEY_REM1: &str = "cca.rem1"; +/// CCA Realm extended measurement key for bank 2 +/// (draft-ydb-rats-cca-endorsements-04 §3.2.3). +pub const CCA_MKEY_REM2: &str = "cca.rem2"; +/// CCA Realm extended measurement key for bank 3 +/// (draft-ydb-rats-cca-endorsements-04 §3.2.3). +pub const CCA_MKEY_REM3: &str = "cca.rem3"; +/// CCA Realm personalization value measurement key +/// (draft-ydb-rats-cca-endorsements-04 §3.2.3). +pub const CCA_MKEY_RPV: &str = "cca.rpv"; + +/// Maximum ROTPK array index from draft-ydb-rats-cca-endorsements-04 §3.1.3.3. +const CCA_ROTPK_MAX_INDEX: u8 = 7; +/// Maximum ROTPK slot index from draft-ydb-rats-cca-endorsements-04 §3.1.3.3. +const CCA_ROTPK_MAX_SLOT: u8 = 5; +/// CCA hash size in bytes from draft-ydb-rats-cca-endorsements-04 §3.1.3.1 and §3.1.3.3. +const CCA_HASH_SIZE_256: usize = 32; +/// CCA hash size in bytes from draft-ydb-rats-cca-endorsements-04 §3.1.3.1 and §3.1.3.3. +const CCA_HASH_SIZE_384: usize = 48; +/// CCA hash size in bytes from draft-ydb-rats-cca-endorsements-04 §3.1.3.1 and §3.1.3.3. +const CCA_HASH_SIZE_512: usize = 64; +/// CCA Realm personalization value size in bytes from draft-ydb-rats-cca-endorsements-04 §3.2.3. +const CCA_RPV_SIZE: usize = 64; +/// CCA Platform Implementation ID size in bytes from draft-ydb-rats-cca-endorsements-04 §3.1.2. +const CCA_IMPLEMENTATION_ID_SIZE: usize = 32; +/// CCA Platform Instance ID (UEID) size in bytes, including the type byte, +/// from draft-ydb-rats-cca-endorsements-04 §3.1.2. +const CCA_INSTANCE_ID_SIZE: usize = 33; +/// UEID `RAND` type byte required by draft-ydb-rats-cca-endorsements-04 §3.1.2. +const CCA_INSTANCE_ID_RAND_TYPE: u8 = 0x01; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum RotpkFamily { + Cm, + Dm, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +struct RotpkMkey { + family: RotpkFamily, + index: u8, + slot: u8, +} + +/// Recognize a CCA Platform measurement key, per draft-ydb-rats-cca-endorsements-04. +pub fn is_cca_platform_mkey(name: &str) -> bool { + match name { + CCA_MKEY_SOFTWARE_COMPONENT + | CCA_MKEY_PLATFORM_CONFIG + | CCA_MKEY_PLATFORM_MANUFACTURING_CONFIG => true, + _ => parse_rotpk_mkey(name).is_some(), + } +} + +fn one_digit_at_most(value: Option<&str>, max: u8) -> Option { + let value = value?; + + let [digit] = value.as_bytes() else { + return None; + }; + if !digit.is_ascii_digit() { + return None; + } + + let value = digit - b'0'; + (value <= max).then_some(value) +} + +fn parse_rotpk_mkey(name: &str) -> Option { + let rest = name.strip_prefix(CCA_MKEY_ROTPK_PREFIX)?; + let mut parts = rest.split('.'); + let family = match parts.next()? { + "CM" => RotpkFamily::Cm, + "DM" => RotpkFamily::Dm, + _ => return None, + }; + let index = one_digit_at_most(parts.next(), CCA_ROTPK_MAX_INDEX)?; + let slot = one_digit_at_most(parts.next(), CCA_ROTPK_MAX_SLOT)?; + if parts.next().is_some() { + return None; + } + + Some(RotpkMkey { + family, + index, + slot, + }) +} + +/// Recognize a CCA Realm measurement key. +pub fn is_cca_realm_mkey(name: &str) -> bool { + matches!( + name, + CCA_MKEY_RIM | CCA_MKEY_REM0 | CCA_MKEY_REM1 | CCA_MKEY_REM2 | CCA_MKEY_REM3 | CCA_MKEY_RPV + ) +} + +fn mkey_name(mkey: &Option) -> Option { + match mkey { + Some(MeasuredElement::Text(s)) => Some(s.clone()), + _ => None, + } +} + +fn has_single_signer_key(mval: &MeasurementValuesMap) -> bool { + single_signer_key_bytes(mval).is_some_and(|bytes| is_cca_hash_size(bytes.len())) +} + +fn single_signer_key_bytes(mval: &MeasurementValuesMap) -> Option<&[u8]> { + match mval.cryptokeys.as_ref()?.as_slice() { + [CryptoKey::Bytes(bytes)] => Some(bytes), + _ => None, + } +} + +fn raw_value_bytes(mval: &MeasurementValuesMap) -> Option<&[u8]> { + match &mval.raw_value { + Some(RawValueChoice::Bytes(bytes)) => Some(bytes), + _ => None, + } +} + +fn has_no_mval_fields_except( + mval: &MeasurementValuesMap, + allow_version: bool, + allow_digests: bool, + allow_raw_value: bool, + allow_name: bool, + allow_cryptokeys: bool, +) -> bool { + (allow_version || mval.version.is_none()) + && (allow_digests || mval.digests.is_none()) + && (allow_raw_value || mval.raw_value.is_none()) + && (allow_name || mval.name.is_none()) + && (allow_cryptokeys || mval.cryptokeys.is_none()) + && mval.svn.is_none() + && mval.flags.is_none() + && mval.mac_addr.is_none() + && mval.ip_addr.is_none() + && mval.serial_number.is_none() + && mval.ueid.is_none() + && mval.uuid.is_none() + && mval.integrity_registers.is_none() + && mval.int_range.is_none() + && mval.extra_entries.is_empty() +} + +fn has_cca_digests(mval: &MeasurementValuesMap) -> bool { + mval.digests.as_ref().is_some_and(|digests| { + !digests.is_empty() + && digests.iter().all(is_cca_digest) + && digests.iter().enumerate().all(|(i, digest)| { + digests + .iter() + .skip(i + 1) + .all(|other| digest.alg() != other.alg()) + }) + }) +} + +fn is_cca_digest(digest: &Digest) -> bool { + matches!(digest.alg(), DigestAlg::Text(_)) && is_cca_hash_size(digest.value().len()) +} + +fn is_cca_hash_size(len: usize) -> bool { + matches!( + len, + CCA_HASH_SIZE_256 | CCA_HASH_SIZE_384 | CCA_HASH_SIZE_512 + ) +} + +fn is_masked_raw_value(mval: &MeasurementValuesMap) -> bool { + matches!(mval.raw_value, Some(RawValueChoice::Masked { .. })) +} + +fn is_bytes_raw_value(mval: &MeasurementValuesMap) -> bool { + raw_value_bytes(mval).is_some() +} + +fn is_bytes_raw_value_of_len(mval: &MeasurementValuesMap, len: usize) -> bool { + raw_value_bytes(mval).is_some_and(|bytes| bytes.len() == len) +} + +fn is_cca_software_component_mval(mval: &MeasurementValuesMap) -> bool { + has_no_mval_fields_except(mval, true, true, false, true, true) + && mval + .version + .as_ref() + .is_none_or(|version| version.version_scheme.is_none()) + && has_cca_digests(mval) + && has_single_signer_key(mval) +} + +fn is_cca_rotpk_mval(mval: &MeasurementValuesMap) -> bool { + has_no_mval_fields_except(mval, false, false, false, false, true) && has_single_signer_key(mval) +} + +fn is_cca_rotpk_evidence_mval(mval: &MeasurementValuesMap) -> bool { + has_no_mval_fields_except(mval, false, false, true, false, false) + && raw_value_bytes(mval).is_some_and(|bytes| is_cca_hash_size(bytes.len())) +} + +fn is_cca_masked_config_reference_mval(mval: &MeasurementValuesMap) -> bool { + has_no_mval_fields_except(mval, false, false, true, false, false) && is_masked_raw_value(mval) +} + +fn is_cca_raw_config_evidence_mval(mval: &MeasurementValuesMap) -> bool { + has_no_mval_fields_except(mval, false, false, true, false, false) && is_bytes_raw_value(mval) +} + +fn is_cca_realm_digest_mval(mval: &MeasurementValuesMap) -> bool { + has_no_mval_fields_except(mval, false, true, false, false, false) && has_cca_digests(mval) +} + +fn is_cca_rpv_mval(mval: &MeasurementValuesMap) -> bool { + has_no_mval_fields_except(mval, false, false, true, false, false) + && is_bytes_raw_value_of_len(mval, CCA_RPV_SIZE) +} + +fn is_valid_cca_platform_reference_measurement(m: &MeasurementMap) -> bool { + if m.authorized_by.is_some() { + return false; + } + + let Some(mkey) = mkey_name(&m.mkey) else { + return false; + }; + + match mkey.as_str() { + CCA_MKEY_SOFTWARE_COMPONENT => is_cca_software_component_mval(&m.mval), + CCA_MKEY_PLATFORM_CONFIG | CCA_MKEY_PLATFORM_MANUFACTURING_CONFIG => { + is_cca_masked_config_reference_mval(&m.mval) + } + _ if parse_rotpk_mkey(&mkey).is_some() => is_cca_rotpk_mval(&m.mval), + _ => false, + } +} + +fn is_valid_cca_platform_evidence_measurement(m: &MeasurementMap) -> bool { + if m.authorized_by.is_some() { + return false; + } + + let Some(mkey) = mkey_name(&m.mkey) else { + return false; + }; + + match mkey.as_str() { + CCA_MKEY_SOFTWARE_COMPONENT => is_cca_software_component_mval(&m.mval), + CCA_MKEY_PLATFORM_CONFIG | CCA_MKEY_PLATFORM_MANUFACTURING_CONFIG => { + is_cca_raw_config_evidence_mval(&m.mval) + } + _ if parse_rotpk_mkey(&mkey).is_some() => is_cca_rotpk_evidence_mval(&m.mval), + _ => false, + } +} + +fn cca_platform_measurements_match(reference: &MeasurementMap, evidence: &MeasurementMap) -> bool { + let Some(mkey) = mkey_name(&reference.mkey) else { + return false; + }; + + match mkey.as_str() { + CCA_MKEY_PLATFORM_CONFIG | CCA_MKEY_PLATFORM_MANUFACTURING_CONFIG => { + raw_value_matches_with_reference_mask( + &reference.mval.raw_value, + &evidence.mval.raw_value, + ) + } + _ if parse_rotpk_mkey(&mkey).is_some() => { + single_signer_key_bytes(&reference.mval) == raw_value_bytes(&evidence.mval) + } + _ => { + crate::validate::core_fields_match(reference, evidence) + && reference.mval.cryptokeys == evidence.mval.cryptokeys + } + } +} + +fn raw_value_matches_with_reference_mask( + reference: &Option, + evidence: &Option, +) -> bool { + match (reference, evidence) { + (Some(RawValueChoice::Masked { value, mask }), Some(RawValueChoice::Bytes(evidence))) => { + masked_bytes_match(value, evidence, mask) + } + _ => reference == evidence, + } +} + +fn masked_bytes_match(reference: &[u8], evidence: &[u8], mask: &[u8]) -> bool { + reference.len() == evidence.len() + && reference.len() == mask.len() + && reference + .iter() + .zip(evidence) + .zip(mask) + .all(|((r, e), m)| (r & m) == (e & m)) +} + +fn is_valid_cca_realm_measurement(m: &MeasurementMap) -> bool { + if m.authorized_by.is_some() { + return false; + } + + let Some(mkey) = mkey_name(&m.mkey) else { + return false; + }; + + match mkey.as_str() { + CCA_MKEY_RIM | CCA_MKEY_REM0 | CCA_MKEY_REM1 | CCA_MKEY_REM2 | CCA_MKEY_REM3 => { + is_cca_realm_digest_mval(&m.mval) + } + CCA_MKEY_RPV => is_cca_rpv_mval(&m.mval), + _ => false, + } +} + +fn valid_rotpk_group(measurements: &[MeasurementMap]) -> bool { + let mut group = None; + let mut slots = [false; (CCA_ROTPK_MAX_SLOT as usize) + 1]; + + for measurement in measurements { + let Some(mkey) = mkey_name(&measurement.mkey) else { + continue; + }; + let Some(rotpk) = parse_rotpk_mkey(&mkey) else { + continue; + }; + + let current_group = (rotpk.family, rotpk.index); + if group.is_some_and(|group| group != current_group) { + return false; + } + group = Some(current_group); + + let slot = usize::from(rotpk.slot); + if slots[slot] { + return false; + } + slots[slot] = true; + } + + true +} + +fn has_duplicate_mkeys(measurements: &[MeasurementMap], recognized: fn(&str) -> bool) -> bool { + measurements.iter().enumerate().any(|(i, measurement)| { + let Some(mkey) = mkey_name(&measurement.mkey) else { + return false; + }; + if !recognized(&mkey) { + return false; + } + measurements + .iter() + .skip(i + 1) + .any(|other| mkey_name(&other.mkey).as_ref() == Some(&mkey)) + }) +} + +fn class_id_bytes(environment: &EnvironmentMap) -> Option<&[u8]> { + match environment.class.as_ref()?.class_id.as_ref()? { + ClassIdChoice::Bytes(bytes) => Some(bytes), + _ => None, + } +} + +/// The subject of a CCA Platform triple is the Implementation ID, encoded as +/// `#6.560(bytes .size 32)` in `environment.class.class-id`, optionally +/// narrowed to a single instance by a `#6.550` UEID +/// (draft-ydb-rats-cca-endorsements-04 §3.1.2). +fn is_valid_cca_platform_environment(environment: &EnvironmentMap) -> bool { + let Some(impl_id) = class_id_bytes(environment) else { + return false; + }; + if impl_id.len() != CCA_IMPLEMENTATION_ID_SIZE { + return false; + } + + match &environment.instance { + None => true, + Some(InstanceIdChoice::Ueid(ueid)) => { + ueid.len() == CCA_INSTANCE_ID_SIZE && ueid[0] == CCA_INSTANCE_ID_RAND_TYPE + } + Some(_) => false, + } +} + +fn is_valid_cca_platform_evidence_environment(environment: &EnvironmentMap) -> bool { + is_valid_cca_platform_environment(environment) + && matches!( + &environment.instance, + Some(InstanceIdChoice::Ueid(ueid)) + if ueid.len() == CCA_INSTANCE_ID_SIZE && ueid[0] == CCA_INSTANCE_ID_RAND_TYPE + ) +} + +/// The subject of a CCA Realm triple is the RIM itself, encoded as +/// `#6.560(cca-hash-type)` in `environment.class.class-id` +/// (draft-ydb-rats-cca-endorsements-04 §3.2.2). The same value is also +/// carried as the mandatory `cca.rim` digest, so the two MUST agree. +fn is_valid_cca_realm_environment(environment: &EnvironmentMap) -> bool { + environment.instance.is_none() + && class_id_bytes(environment).is_some_and(|rim| is_cca_hash_size(rim.len())) +} + +/// The `cca.rim` measurement may report the RIM under more than one hash +/// algorithm, and the class-id carries exactly one of those values, so one +/// matching digest is what the linkage requires. +fn realm_rim_matches_environment(environment: &EnvironmentMap, rim: &MeasurementMap) -> bool { + let Some(class_rim) = class_id_bytes(environment) else { + return false; + }; + rim.mval + .digests + .as_ref() + .is_some_and(|digests| digests.iter().any(|digest| digest.value() == class_rim)) +} + +/// Profile implementation for Arm CCA Platform endorsements. +#[derive(Clone, Debug, PartialEq)] +pub struct CcaPlatformProfile { + id: ProfileChoice, +} + +impl CcaPlatformProfile { + /// Construct a new CCA Platform profile instance. + pub fn new() -> Self { + Self { + id: ProfileChoice::Uri(CCA_PLATFORM_PROFILE_URI.into()), + } + } +} + +impl Default for CcaPlatformProfile { + fn default() -> Self { + Self::new() + } +} + +/// Profile implementation for Arm CCA Realm endorsements. +#[derive(Clone, Debug, PartialEq)] +pub struct CcaRealmProfile { + id: ProfileChoice, +} + +impl CcaRealmProfile { + /// Construct a new CCA Realm profile instance. + pub fn new() -> Self { + Self { + id: ProfileChoice::Uri(CCA_REALM_PROFILE_URI.into()), + } + } +} + +impl Default for CcaRealmProfile { + fn default() -> Self { + Self::new() + } +} + +impl Profile for CcaPlatformProfile { + fn identifier(&self) -> &ProfileChoice { + &self.id + } + + fn reference_triple_valid(&self, triple: &ReferenceTriple) -> bool { + if !is_valid_cca_platform_environment(triple.environment()) { + return false; + } + + let mut software_component_count = 0usize; + let mut platform_config_count = 0usize; + let mut manufacturing_config_count = 0usize; + let mut rotpk_count = 0usize; + + for measurement in triple.measurements() { + let Some(mkey) = mkey_name(&measurement.mkey) else { + continue; + }; + + if !is_cca_platform_mkey(&mkey) { + continue; + } + if !is_valid_cca_platform_reference_measurement(measurement) { + return false; + } + + match mkey.as_str() { + CCA_MKEY_SOFTWARE_COMPONENT => software_component_count += 1, + CCA_MKEY_PLATFORM_CONFIG => platform_config_count += 1, + CCA_MKEY_PLATFORM_MANUFACTURING_CONFIG => manufacturing_config_count += 1, + _ => rotpk_count += 1, + } + } + + // §3.1.3.3: each ROTPK array entry is carried in its own reference + // triple, so a ROTPK triple describes no other platform measurement. + if rotpk_count > 0 { + return software_component_count == 0 + && platform_config_count == 0 + && manufacturing_config_count == 0 + && rotpk_count == triple.measurements().len() + && valid_rotpk_group(triple.measurements()); + } + + // §3.1.3: a single reference triple MUST completely describe the CCA + // Platform measurements — a mandatory platform configuration + // (§3.1.3.2, "only one") and the platform software components + // (§3.1.3.1), plus at most one manufacturing configuration (§3.1.3.4). + software_component_count >= 1 + && platform_config_count == 1 + && manufacturing_config_count <= 1 + } + + fn evidence_claim_valid(&self, claim: &EvidenceClaim) -> bool { + is_valid_cca_platform_evidence_environment(&claim.environment) + } + + /// §3.1.4: the IAK verification key endorsement MUST identify both the + /// Implementation and Instance and MUST carry exactly one key, encoded + /// as `tagged-pkix-base64-key-type` (`#6.554`). + fn attest_key_triple_valid(&self, triple: &AttestKeyTriple) -> bool { + is_valid_cca_platform_environment(triple.environment()) + && triple.environment().instance.is_some() + && matches!(triple.keys(), [CryptoKey::PkixBase64Key(_)]) + } + + fn match_measurement( + &self, + reference: &MeasurementMap, + evidence: &MeasurementMap, + _ctx: &MatchContext, + ) -> Option { + let ref_mkey = mkey_name(&reference.mkey)?; + let ev_mkey = mkey_name(&evidence.mkey)?; + + if ref_mkey != ev_mkey { + return Some(false); + } + if !is_cca_platform_mkey(&ref_mkey) { + return None; + } + if !is_valid_cca_platform_reference_measurement(reference) + || !is_valid_cca_platform_evidence_measurement(evidence) + { + return Some(false); + } + + Some(cca_platform_measurements_match(reference, evidence)) + } +} + +impl Profile for CcaRealmProfile { + fn identifier(&self) -> &ProfileChoice { + &self.id + } + + fn reference_triple_valid(&self, triple: &ReferenceTriple) -> bool { + if !is_valid_cca_realm_environment(triple.environment()) { + return false; + } + + let mut has_rim = false; + + for measurement in triple.measurements() { + let Some(mkey) = mkey_name(&measurement.mkey) else { + return false; + }; + + if !is_cca_realm_mkey(&mkey) || !is_valid_cca_realm_measurement(measurement) { + return false; + } + // §3.2.2: the environment class-id carries the RIM, so the + // mandatory `cca.rim` measurement MUST report the same value. + if mkey == CCA_MKEY_RIM { + if !realm_rim_matches_environment(triple.environment(), measurement) { + return false; + } + has_rim = true; + } + } + + has_rim && !has_duplicate_mkeys(triple.measurements(), is_cca_realm_mkey) + } + + fn evidence_claim_valid(&self, claim: &EvidenceClaim) -> bool { + is_valid_cca_realm_environment(&claim.environment) + } + + fn match_measurement( + &self, + reference: &MeasurementMap, + evidence: &MeasurementMap, + _ctx: &MatchContext, + ) -> Option { + let ref_mkey = mkey_name(&reference.mkey)?; + let ev_mkey = mkey_name(&evidence.mkey)?; + + if ref_mkey != ev_mkey { + return Some(false); + } + if !is_cca_realm_mkey(&ref_mkey) { + return None; + } + if !is_valid_cca_realm_measurement(reference) || !is_valid_cca_realm_measurement(evidence) { + return Some(false); + } + + Some(crate::validate::core_fields_match(reference, evidence)) + } +} diff --git a/corim/src/validate.rs b/corim/src/validate.rs index a01685c..9dabf9a 100644 --- a/corim/src/validate.rs +++ b/corim/src/validate.rs @@ -315,9 +315,15 @@ pub struct EvidenceClaim { /// - `None` from the profile — defer to the default per-pair logic /// (the same comparison performed by [`match_reference_values`]). /// -/// The profile is consulted independently for each (reference, evidence) -/// pair within a triple. Pass `None` for `profile` to get behavior -/// identical to [`match_reference_values`]. +/// Before any per-pair matching, the profile's +/// [`Profile::reference_triple_valid`] hook is called once for each +/// reference triple. A `false` result skips that whole triple. The default +/// hook returns `true`. For each candidate evidence claim, the profile's +/// [`Profile::evidence_claim_valid`] hook is also called before generic +/// environment matching; `false` skips that evidence claim. The default hook +/// returns `true`, so profiles with no triple- or evidence-level rules behave +/// as if only per-pair matching were customized. Pass `None` for `profile` to +/// get behavior identical to [`match_reference_values`]. /// /// Profile lookup is the caller's responsibility: /// @@ -341,7 +347,15 @@ pub fn match_reference_values_with_profile( let mut corroborated = Vec::new(); for triple in ref_triples { + if profile.is_some_and(|p| !p.reference_triple_valid(triple)) { + continue; + } + for ev in evidence { + if profile.is_some_and(|p| !p.evidence_claim_valid(ev)) { + continue; + } + if !environment_matches(triple.environment(), &ev.environment) { continue; } @@ -414,9 +428,10 @@ pub fn apply_endorsement_series( } /// Like [`apply_endorsement_series`] but consults a profile's -/// [`Profile::match_measurement`] hook when comparing each series -/// `condition` entry against evidence. Per-pair semantics are identical -/// to those of [`match_reference_values_with_profile`]. +/// [`Profile::evidence_claim_valid`] hook before generic environment +/// matching and [`Profile::match_measurement`] hook when comparing each +/// series `condition` entry against evidence. Per-pair semantics are +/// identical to those of [`match_reference_values_with_profile`]. /// /// Pass `None::<&dyn Profile>` for `profile` to get behavior identical /// to [`apply_endorsement_series`]. @@ -433,6 +448,7 @@ pub fn apply_endorsement_series_with_profile( let matching_evidence: Vec<_> = evidence .iter() + .filter(|ev| profile.is_none_or(|p| p.evidence_claim_valid(ev))) .filter(|ev| environment_matches(&condition.environment, &ev.environment)) .collect(); diff --git a/corim/tests/profile_cca_tests.rs b/corim/tests/profile_cca_tests.rs new file mode 100644 index 0000000..203dbf7 --- /dev/null +++ b/corim/tests/profile_cca_tests.rs @@ -0,0 +1,966 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#![cfg(feature = "profile-cca")] + +use corim::profile::cca::{ + is_cca_platform_mkey, is_cca_realm_mkey, CcaPlatformProfile, CcaRealmProfile, + CCA_PLATFORM_PROFILE_URI, CCA_REALM_PROFILE_URI, +}; +use corim::profile::{MatchContext, Profile}; +use corim::types::common::{ClassIdChoice, CryptoKey, InstanceIdChoice, MeasuredElement}; +use corim::types::corim::ProfileChoice; +use corim::types::environment::{ClassMap, EnvironmentMap}; +use corim::types::measurement::{Digest, MeasurementMap, MeasurementValuesMap, RawValueChoice}; +use corim::types::triples::ReferenceTriple; +use corim::validate::{match_reference_values_with_profile, EvidenceClaim}; + +fn measurement_with_mkey(mkey: &str, digest_val: &[u8]) -> MeasurementMap { + MeasurementMap { + mkey: Some(MeasuredElement::Text(mkey.into())), + mval: MeasurementValuesMap { + digests: Some(vec![Digest::new_text("sha-256", digest_val.to_vec())]), + ..MeasurementValuesMap::default() + }, + authorized_by: None, + } +} + +fn software_component_measurement(name: &str, digest_val: &[u8], signer: &[u8]) -> MeasurementMap { + MeasurementMap { + mkey: Some(MeasuredElement::Text(name.into())), + mval: MeasurementValuesMap { + digests: Some(vec![Digest::new_text("sha-256", digest_val.to_vec())]), + cryptokeys: Some(vec![CryptoKey::Bytes(signer.to_vec())]), + ..MeasurementValuesMap::default() + }, + authorized_by: None, + } +} + +fn rotpk_measurement(mkey: &str, key_bytes: &[u8]) -> MeasurementMap { + MeasurementMap { + mkey: Some(MeasuredElement::Text(mkey.into())), + mval: MeasurementValuesMap { + cryptokeys: Some(vec![CryptoKey::Bytes(key_bytes.to_vec())]), + ..MeasurementValuesMap::default() + }, + authorized_by: None, + } +} + +fn raw_value_measurement(mkey: &str, value: &[u8]) -> MeasurementMap { + MeasurementMap { + mkey: Some(MeasuredElement::Text(mkey.into())), + mval: MeasurementValuesMap { + raw_value: Some(RawValueChoice::Bytes(value.to_vec())), + ..MeasurementValuesMap::default() + }, + authorized_by: None, + } +} + +fn masked_raw_value_measurement(mkey: &str, value: &[u8], mask: &[u8]) -> MeasurementMap { + MeasurementMap { + mkey: Some(MeasuredElement::Text(mkey.into())), + mval: MeasurementValuesMap { + raw_value: Some(RawValueChoice::Masked { + value: value.to_vec(), + mask: mask.to_vec(), + }), + ..MeasurementValuesMap::default() + }, + authorized_by: None, + } +} + +fn environment_with_class_id(class_id: &[u8]) -> EnvironmentMap { + EnvironmentMap { + class: Some(ClassMap { + class_id: Some(ClassIdChoice::Bytes(class_id.to_vec())), + ..ClassMap::default() + }), + instance: None, + group: None, + } +} + +/// Platform triples are keyed by a 32-byte Implementation ID +/// (draft-ydb-rats-cca-endorsements-04 §3.1.2). +fn platform_environment() -> EnvironmentMap { + environment_with_class_id(&[0x5A; 32]) +} + +fn platform_evidence_environment() -> EnvironmentMap { + let mut environment = platform_environment(); + environment.instance = Some(InstanceIdChoice::Ueid({ + let mut ueid = vec![0x42; 33]; + ueid[0] = 0x01; + ueid + })); + environment +} + +/// Realm triples carry the RIM itself as `class-id` +/// (draft-ydb-rats-cca-endorsements-04 §3.2.2). +fn realm_environment(rim: &[u8]) -> EnvironmentMap { + environment_with_class_id(rim) +} + +#[test] +fn platform_profile_uses_cca_platform_uri() { + let profile = CcaPlatformProfile::new(); + assert_eq!( + profile.identifier(), + &ProfileChoice::Uri(CCA_PLATFORM_PROFILE_URI.into()) + ); +} + +#[test] +fn realm_profile_uses_cca_realm_uri() { + let profile = CcaRealmProfile::new(); + assert_eq!( + profile.identifier(), + &ProfileChoice::Uri(CCA_REALM_PROFILE_URI.into()) + ); +} + +#[test] +fn recognized_platform_mkeys_include_software_component_and_config() { + assert!(is_cca_platform_mkey("cca.software-component")); + assert!(is_cca_platform_mkey("cca.platform-config")); + assert!(is_cca_platform_mkey("cca.rotpk.CM.2.3")); + assert!(is_cca_platform_mkey("cca.rotpk.DM.7.5")); + assert!(is_cca_platform_mkey("cca.platform-manufacturing-config")); + assert!(!is_cca_platform_mkey("cca.unknown")); + assert!(!is_cca_platform_mkey("cca.rotpk.CM.8.0")); + assert!(!is_cca_platform_mkey("cca.rotpk.CM.2.6")); + assert!(!is_cca_platform_mkey("cca.rotpk.CM.02.3")); + assert!(!is_cca_platform_mkey("cca.rotpk.CM.2.03")); +} + +#[test] +fn recognized_realm_mkeys_include_rim_rem_and_rpv() { + assert!(is_cca_realm_mkey("cca.rim")); + assert!(is_cca_realm_mkey("cca.rem0")); + assert!(is_cca_realm_mkey("cca.rem3")); + assert!(is_cca_realm_mkey("cca.rpv")); + assert!(!is_cca_realm_mkey("cca.rem10")); + assert!(!is_cca_realm_mkey("cca.rem00")); +} + +#[test] +fn platform_match_accepts_same_cca_mkey_and_same_core_values() { + let profile = CcaPlatformProfile::new(); + let reference = + software_component_measurement("cca.software-component", &[0x11; 32], &[0xAA; 32]); + let evidence = + software_component_measurement("cca.software-component", &[0x11; 32], &[0xAA; 32]); + + assert_eq!( + profile.match_measurement(&reference, &evidence, &MatchContext::new()), + Some(true) + ); +} + +#[test] +fn platform_match_rejects_mkey_mismatch() { + let profile = CcaPlatformProfile::new(); + let reference = measurement_with_mkey("cca.software-component", &[0x11, 0x22, 0x33]); + let evidence = measurement_with_mkey("cca.platform-config", &[0x11, 0x22, 0x33]); + + assert_eq!( + profile.match_measurement(&reference, &evidence, &MatchContext::new()), + Some(false) + ); +} + +#[test] +fn platform_match_defers_for_non_cca_mkey() { + let profile = CcaPlatformProfile::new(); + let reference = measurement_with_mkey("tee.something", &[0x11, 0x22, 0x33]); + let evidence = measurement_with_mkey("tee.something", &[0x11, 0x22, 0x33]); + + assert_eq!( + profile.match_measurement(&reference, &evidence, &MatchContext::new()), + None + ); +} + +#[test] +fn platform_match_rejects_invalid_cca_structures() { + let profile = CcaPlatformProfile::new(); + let reference = measurement_with_mkey("cca.rotpk.CM.2.3", &[0x11, 0x22, 0x33]); + let evidence = raw_value_measurement("cca.rotpk.CM.2.3", &[0xAA; 32]); + + assert_eq!( + profile.match_measurement(&reference, &evidence, &MatchContext::new()), + Some(false) + ); +} + +#[test] +fn platform_match_accepts_rotpk_reference_against_raw_value_evidence() { + let profile = CcaPlatformProfile::new(); + let reference = rotpk_measurement("cca.rotpk.CM.2.3", &[0xAA; 32]); + let evidence = raw_value_measurement("cca.rotpk.CM.2.3", &[0xAA; 32]); + + assert_eq!( + profile.match_measurement(&reference, &evidence, &MatchContext::new()), + Some(true) + ); +} + +#[test] +fn platform_match_rejects_different_software_component_signer_id() { + let profile = CcaPlatformProfile::new(); + let reference = + software_component_measurement("cca.software-component", &[0x11; 32], &[0xAA; 32]); + let evidence = + software_component_measurement("cca.software-component", &[0x11; 32], &[0xBB; 32]); + + assert_eq!( + profile.match_measurement(&reference, &evidence, &MatchContext::new()), + Some(false) + ); +} + +#[test] +fn platform_match_rejects_different_rotpk_key() { + let profile = CcaPlatformProfile::new(); + let reference = rotpk_measurement("cca.rotpk.CM.2.3", &[0xAA; 32]); + let evidence = raw_value_measurement("cca.rotpk.CM.2.3", &[0xBB; 32]); + + assert_eq!( + profile.match_measurement(&reference, &evidence, &MatchContext::new()), + Some(false) + ); +} + +#[test] +fn platform_match_accepts_masked_config_reference_against_unmasked_evidence() { + let profile = CcaPlatformProfile::new(); + let reference = + masked_raw_value_measurement("cca.platform-config", &[0xA0, 0x05], &[0xF0, 0x00]); + let evidence = raw_value_measurement("cca.platform-config", &[0xAF, 0xFF]); + + assert_eq!( + profile.match_measurement(&reference, &evidence, &MatchContext::new()), + Some(true) + ); +} + +#[test] +fn platform_match_rejects_masked_config_evidence() { + let profile = CcaPlatformProfile::new(); + let reference = + masked_raw_value_measurement("cca.platform-config", &[0xA0, 0x05], &[0xF0, 0x00]); + let evidence = + masked_raw_value_measurement("cca.platform-config", &[0xAF, 0xFF], &[0xFF, 0xFF]); + + assert_eq!( + profile.match_measurement(&reference, &evidence, &MatchContext::new()), + Some(false) + ); +} + +fn platform_evidence() -> Vec { + vec![EvidenceClaim { + environment: platform_evidence_environment(), + measurements: vec![ + software_component_measurement("cca.software-component", &[0x11; 32], &[0xAA; 32]), + raw_value_measurement("cca.platform-config", &[0xAF, 0xFF]), + ], + }] +} + +fn platform_claims(profile: &CcaPlatformProfile, measurements: Vec) -> usize { + let triples = vec![ReferenceTriple::new(platform_environment(), measurements)]; + + match_reference_values_with_profile( + &triples, + &platform_evidence(), + Some(profile), + &MatchContext::new(), + ) + .len() +} + +#[test] +fn platform_profile_accepts_complete_reference_triple() { + let profile = CcaPlatformProfile::new(); + + assert_eq!( + platform_claims( + &profile, + vec![ + software_component_measurement("cca.software-component", &[0x11; 32], &[0xAA; 32]), + masked_raw_value_measurement("cca.platform-config", &[0xA0, 0x05], &[0xF0, 0x00]), + ], + ), + 1 + ); +} + +#[test] +fn platform_profile_rejects_duplicate_config_measurements() { + let profile = CcaPlatformProfile::new(); + let config = masked_raw_value_measurement("cca.platform-config", &[0xA0, 0x05], &[0xF0, 0x00]); + + assert_eq!( + platform_claims( + &profile, + vec![ + software_component_measurement("cca.software-component", &[0x11; 32], &[0xAA; 32]), + config.clone(), + config, + ], + ), + 0 + ); +} + +#[test] +fn platform_profile_rejects_triple_without_platform_config() { + let profile = CcaPlatformProfile::new(); + + assert_eq!( + platform_claims( + &profile, + vec![software_component_measurement( + "cca.software-component", + &[0x11; 32], + &[0xAA; 32] + )], + ), + 0 + ); +} + +#[test] +fn platform_profile_rejects_triple_without_software_component() { + let profile = CcaPlatformProfile::new(); + + assert_eq!( + platform_claims( + &profile, + vec![masked_raw_value_measurement( + "cca.platform-config", + &[0xA0, 0x05], + &[0xF0, 0x00] + )], + ), + 0 + ); +} + +#[test] +fn platform_profile_rejects_triple_without_cca_measurements() { + let profile = CcaPlatformProfile::new(); + + assert_eq!( + platform_claims( + &profile, + vec![measurement_with_mkey("tee.something", &[0x11; 32])], + ), + 0 + ); +} + +#[test] +fn platform_profile_accepts_standalone_rotpk_triple() { + let profile = CcaPlatformProfile::new(); + let rotpk = rotpk_measurement("cca.rotpk.CM.2.3", &[0xAA; 32]); + let triples = vec![ReferenceTriple::new( + platform_environment(), + vec![rotpk.clone()], + )]; + let evidence = vec![EvidenceClaim { + environment: platform_evidence_environment(), + measurements: vec![raw_value_measurement("cca.rotpk.CM.2.3", &[0xAA; 32])], + }]; + + let claims = match_reference_values_with_profile( + &triples, + &evidence, + Some(&profile), + &MatchContext::new(), + ); + + assert_eq!(claims.len(), 1); +} + +#[test] +fn platform_profile_accepts_rotpk_triple_for_one_array_entry() { + let profile = CcaPlatformProfile::new(); + let first = rotpk_measurement("cca.rotpk.CM.2.0", &[0xAA; 32]); + let second = rotpk_measurement("cca.rotpk.CM.2.1", &[0xBB; 32]); + let triples = vec![ReferenceTriple::new( + platform_environment(), + vec![first.clone(), second.clone()], + )]; + let evidence = vec![EvidenceClaim { + environment: platform_evidence_environment(), + measurements: vec![ + raw_value_measurement("cca.rotpk.CM.2.0", &[0xAA; 32]), + raw_value_measurement("cca.rotpk.CM.2.1", &[0xBB; 32]), + ], + }]; + + let claims = match_reference_values_with_profile( + &triples, + &evidence, + Some(&profile), + &MatchContext::new(), + ); + + assert_eq!(claims.len(), 1); + assert_eq!(claims[0].measurements.len(), 2); +} + +#[test] +fn platform_profile_rejects_rotpk_mixed_array_entries() { + let profile = CcaPlatformProfile::new(); + let first = rotpk_measurement("cca.rotpk.CM.2.0", &[0xAA; 32]); + let second = rotpk_measurement("cca.rotpk.CM.3.0", &[0xBB; 32]); + let third = rotpk_measurement("cca.rotpk.DM.2.1", &[0xCC; 32]); + + for measurements in [vec![first.clone(), second], vec![first.clone(), third]] { + let triples = vec![ReferenceTriple::new( + platform_environment(), + measurements.clone(), + )]; + let evidence = vec![EvidenceClaim { + environment: platform_evidence_environment(), + measurements, + }]; + + let claims = match_reference_values_with_profile( + &triples, + &evidence, + Some(&profile), + &MatchContext::new(), + ); + + assert!(claims.is_empty()); + } +} + +#[test] +fn platform_profile_rejects_rotpk_mixed_with_unknown_measurement() { + let profile = CcaPlatformProfile::new(); + let rotpk = rotpk_measurement("cca.rotpk.CM.2.0", &[0xAA; 32]); + let triples = vec![ReferenceTriple::new( + platform_environment(), + vec![rotpk, measurement_with_mkey("tee.something", &[0x11; 32])], + )]; + let evidence = vec![EvidenceClaim { + environment: platform_evidence_environment(), + measurements: vec![raw_value_measurement("cca.rotpk.CM.2.0", &[0xAA; 32])], + }]; + + let claims = match_reference_values_with_profile( + &triples, + &evidence, + Some(&profile), + &MatchContext::new(), + ); + + assert!(claims.is_empty()); +} + +#[test] +fn platform_profile_rejects_rotpk_mixed_with_platform_measurements() { + let profile = CcaPlatformProfile::new(); + + assert_eq!( + platform_claims( + &profile, + vec![ + software_component_measurement("cca.software-component", &[0x11; 32], &[0xAA; 32]), + masked_raw_value_measurement("cca.platform-config", &[0xA0, 0x05], &[0xF0, 0x00]), + rotpk_measurement("cca.rotpk.CM.2.3", &[0xAA; 32]), + ], + ), + 0 + ); +} + +#[test] +fn platform_profile_rejects_triple_without_implementation_id() { + let profile = CcaPlatformProfile::new(); + let triples = vec![ReferenceTriple::new( + EnvironmentMap::for_class("ACME", "Platform"), + vec![ + software_component_measurement("cca.software-component", &[0x11; 32], &[0xAA; 32]), + masked_raw_value_measurement("cca.platform-config", &[0xA0, 0x05], &[0xF0, 0x00]), + ], + )]; + let evidence = vec![EvidenceClaim { + environment: EnvironmentMap::for_class("ACME", "Platform"), + measurements: platform_evidence().remove(0).measurements, + }]; + + let claims = match_reference_values_with_profile( + &triples, + &evidence, + Some(&profile), + &MatchContext::new(), + ); + + assert!(claims.is_empty()); +} + +#[test] +fn platform_profile_rejects_triple_with_non_ueid_instance() { + let profile = CcaPlatformProfile::new(); + let mut environment = platform_environment(); + environment.instance = Some(InstanceIdChoice::Bytes(vec![0x01; 32])); + let triples = vec![ReferenceTriple::new( + environment.clone(), + vec![ + software_component_measurement("cca.software-component", &[0x11; 32], &[0xAA; 32]), + masked_raw_value_measurement("cca.platform-config", &[0xA0, 0x05], &[0xF0, 0x00]), + ], + )]; + let evidence = vec![EvidenceClaim { + environment, + measurements: platform_evidence().remove(0).measurements, + }]; + + let claims = match_reference_values_with_profile( + &triples, + &evidence, + Some(&profile), + &MatchContext::new(), + ); + + assert!(claims.is_empty()); +} + +#[test] +fn platform_profile_rejects_evidence_without_instance() { + let profile = CcaPlatformProfile::new(); + let triples = vec![ReferenceTriple::new( + platform_environment(), + vec![ + software_component_measurement("cca.software-component", &[0x11; 32], &[0xAA; 32]), + masked_raw_value_measurement("cca.platform-config", &[0xA0, 0x05], &[0xF0, 0x00]), + ], + )]; + let evidence = vec![EvidenceClaim { + environment: platform_environment(), + measurements: platform_evidence().remove(0).measurements, + }]; + + let claims = match_reference_values_with_profile( + &triples, + &evidence, + Some(&profile), + &MatchContext::new(), + ); + + assert!(claims.is_empty()); +} + +#[test] +fn platform_profile_rejects_evidence_with_non_ueid_instance() { + let profile = CcaPlatformProfile::new(); + let triples = vec![ReferenceTriple::new( + platform_environment(), + vec![ + software_component_measurement("cca.software-component", &[0x11; 32], &[0xAA; 32]), + masked_raw_value_measurement("cca.platform-config", &[0xA0, 0x05], &[0xF0, 0x00]), + ], + )]; + let mut evidence_environment = platform_environment(); + evidence_environment.instance = Some(InstanceIdChoice::Bytes(vec![0x01; 33])); + let evidence = vec![EvidenceClaim { + environment: evidence_environment, + measurements: platform_evidence().remove(0).measurements, + }]; + + let claims = match_reference_values_with_profile( + &triples, + &evidence, + Some(&profile), + &MatchContext::new(), + ); + + assert!(claims.is_empty()); +} + +#[test] +fn platform_profile_rejects_malformed_reference_measurement_in_triple() { + let profile = CcaPlatformProfile::new(); + let mut malformed_software_component = + software_component_measurement("cca.software-component", &[0x11; 32], &[0xAA; 32]); + malformed_software_component.mval.cryptokeys = None; + + assert_eq!( + platform_claims( + &profile, + vec![ + malformed_software_component, + masked_raw_value_measurement("cca.platform-config", &[0xA0, 0x05], &[0xF0, 0x00]), + ], + ), + 0 + ); +} + +#[test] +fn platform_match_rejects_unmasked_config_reference() { + let profile = CcaPlatformProfile::new(); + let reference = raw_value_measurement("cca.platform-config", &[0xAA, 0xBB]); + let evidence = raw_value_measurement("cca.platform-config", &[0xAA, 0xBB]); + + assert_eq!( + profile.match_measurement(&reference, &evidence, &MatchContext::new()), + Some(false) + ); +} + +#[test] +fn platform_match_rejects_software_component_digest_with_integer_alg() { + let profile = CcaPlatformProfile::new(); + let reference = MeasurementMap { + mkey: Some(MeasuredElement::Text("cca.software-component".into())), + mval: MeasurementValuesMap { + digests: Some(vec![Digest::new(7, vec![0x11; 32])]), + cryptokeys: Some(vec![CryptoKey::Bytes(vec![0xAA; 32])]), + ..MeasurementValuesMap::default() + }, + authorized_by: None, + }; + let evidence = + software_component_measurement("cca.software-component", &[0x11; 32], &[0xAA; 32]); + + assert_eq!( + profile.match_measurement(&reference, &evidence, &MatchContext::new()), + Some(false) + ); +} + +#[test] +fn platform_match_rejects_software_component_extra_mval_field() { + let profile = CcaPlatformProfile::new(); + let mut reference = + software_component_measurement("cca.software-component", &[0x11; 32], &[0xAA; 32]); + reference.mval.raw_value = Some(RawValueChoice::Bytes(vec![0xCC; 32])); + let evidence = + software_component_measurement("cca.software-component", &[0x11; 32], &[0xAA; 32]); + + assert_eq!( + profile.match_measurement(&reference, &evidence, &MatchContext::new()), + Some(false) + ); +} + +#[test] +fn platform_match_rejects_rotpk_extra_mval_field() { + let profile = CcaPlatformProfile::new(); + let mut reference = rotpk_measurement("cca.rotpk.CM.2.3", &[0xAA; 32]); + reference.mval.raw_value = Some(RawValueChoice::Bytes(vec![0xCC; 32])); + let evidence = raw_value_measurement("cca.rotpk.CM.2.3", &[0xAA; 32]); + + assert_eq!( + profile.match_measurement(&reference, &evidence, &MatchContext::new()), + Some(false) + ); +} + +#[test] +fn platform_match_rejects_multiple_software_component_signer_ids() { + let profile = CcaPlatformProfile::new(); + let mut reference = + software_component_measurement("cca.software-component", &[0x11; 32], &[0xAA; 32]); + reference + .mval + .cryptokeys + .as_mut() + .unwrap() + .push(CryptoKey::Bytes(vec![0xBB; 32])); + let evidence = + software_component_measurement("cca.software-component", &[0x11; 32], &[0xAA; 32]); + + assert_eq!( + profile.match_measurement(&reference, &evidence, &MatchContext::new()), + Some(false) + ); +} + +#[test] +fn platform_match_rejects_authorized_by() { + let profile = CcaPlatformProfile::new(); + let mut reference = rotpk_measurement("cca.rotpk.CM.2.3", &[0xAA; 32]); + reference.authorized_by = Some(vec![CryptoKey::Bytes(vec![0xCC; 32])]); + let evidence = raw_value_measurement("cca.rotpk.CM.2.3", &[0xAA; 32]); + + assert_eq!( + profile.match_measurement(&reference, &evidence, &MatchContext::new()), + Some(false) + ); +} + +#[test] +fn realm_match_rejects_raw_value_violation() { + let profile = CcaRealmProfile::new(); + let reference = raw_value_measurement("cca.rpv", &[0xAA; 64]); + let evidence = measurement_with_mkey("cca.rpv", b"def"); + + assert_eq!( + profile.match_measurement(&reference, &evidence, &MatchContext::new()), + Some(false) + ); +} + +#[test] +fn realm_match_rejects_short_rpv() { + let profile = CcaRealmProfile::new(); + let reference = raw_value_measurement("cca.rpv", b"abc"); + let evidence = raw_value_measurement("cca.rpv", b"abc"); + + assert_eq!( + profile.match_measurement(&reference, &evidence, &MatchContext::new()), + Some(false) + ); +} + +fn realm_claims( + profile: &CcaRealmProfile, + environment: EnvironmentMap, + measurements: Vec, +) -> usize { + let triples = vec![ReferenceTriple::new( + environment.clone(), + measurements.clone(), + )]; + let evidence = vec![EvidenceClaim { + environment, + measurements, + }]; + + match_reference_values_with_profile(&triples, &evidence, Some(profile), &MatchContext::new()) + .len() +} + +#[test] +fn realm_profile_rejects_reference_triple_without_mandatory_rim() { + let profile = CcaRealmProfile::new(); + + assert_eq!( + realm_claims( + &profile, + realm_environment(&[0xAA; 32]), + vec![measurement_with_mkey("cca.rem0", &[0x11; 32])], + ), + 0 + ); +} + +#[test] +fn realm_profile_rejects_reference_triple_without_cca_measurements() { + let profile = CcaRealmProfile::new(); + + assert_eq!( + realm_claims( + &profile, + realm_environment(&[0xAA; 32]), + vec![measurement_with_mkey("tee.something", &[0x11; 32])], + ), + 0 + ); +} + +#[test] +fn realm_profile_accepts_reference_triple_with_mandatory_rim() { + let profile = CcaRealmProfile::new(); + let rim_value = [0xAA; 32]; + + assert_eq!( + realm_claims( + &profile, + realm_environment(&rim_value), + vec![ + measurement_with_mkey("cca.rim", &rim_value), + measurement_with_mkey("cca.rem0", &[0x11; 32]), + ], + ), + 1 + ); +} + +#[test] +fn realm_profile_rejects_rim_that_disagrees_with_environment_class_id() { + let profile = CcaRealmProfile::new(); + + assert_eq!( + realm_claims( + &profile, + realm_environment(&[0xAA; 32]), + vec![measurement_with_mkey("cca.rim", &[0xBB; 32])], + ), + 0 + ); +} + +#[test] +fn realm_profile_rejects_reference_triple_without_rim_class_id() { + let profile = CcaRealmProfile::new(); + + assert_eq!( + realm_claims( + &profile, + EnvironmentMap::for_class("ACME", "Realm"), + vec![measurement_with_mkey("cca.rim", &[0xAA; 32])], + ), + 0 + ); +} + +#[test] +fn realm_profile_rejects_duplicate_realm_measurements() { + let profile = CcaRealmProfile::new(); + let rim_value = [0xAA; 32]; + let rem = measurement_with_mkey("cca.rem0", &[0x11; 32]); + + assert_eq!( + realm_claims( + &profile, + realm_environment(&rim_value), + vec![ + measurement_with_mkey("cca.rim", &rim_value), + rem.clone(), + rem, + ], + ), + 0 + ); +} + +#[test] +fn realm_profile_rejects_unknown_measurement_in_reference_triple() { + let profile = CcaRealmProfile::new(); + let rim_value = [0xAA; 32]; + + assert_eq!( + realm_claims( + &profile, + realm_environment(&rim_value), + vec![ + measurement_with_mkey("cca.rim", &rim_value), + measurement_with_mkey("tee.something", &[0x11; 32]), + ], + ), + 0 + ); +} + +#[test] +fn realm_profile_rejects_malformed_rim_in_reference_triple() { + let profile = CcaRealmProfile::new(); + let rim_value = [0xAA; 32]; + let mut rim = measurement_with_mkey("cca.rim", &rim_value); + rim.authorized_by = Some(vec![CryptoKey::Bytes(vec![0xCC; 32])]); + + assert_eq!( + realm_claims( + &profile, + realm_environment(&rim_value), + vec![rim, measurement_with_mkey("cca.rem0", &[0x11; 32])], + ), + 0 + ); +} + +#[test] +fn realm_profile_rejects_evidence_with_instance() { + let profile = CcaRealmProfile::new(); + let rim_value = [0xAA; 32]; + let triples = vec![ReferenceTriple::new( + realm_environment(&rim_value), + vec![measurement_with_mkey("cca.rim", &rim_value)], + )]; + let mut evidence_environment = realm_environment(&rim_value); + evidence_environment.instance = Some(InstanceIdChoice::Ueid({ + let mut ueid = vec![0x42; 33]; + ueid[0] = 0x01; + ueid + })); + let evidence = vec![EvidenceClaim { + environment: evidence_environment, + measurements: vec![measurement_with_mkey("cca.rim", &rim_value)], + }]; + + let claims = match_reference_values_with_profile( + &triples, + &evidence, + Some(&profile), + &MatchContext::new(), + ); + + assert!(claims.is_empty()); +} + +#[test] +fn realm_match_rejects_masked_rpv() { + let profile = CcaRealmProfile::new(); + let reference = masked_raw_value_measurement("cca.rpv", &[0xAA; 32], &[0xFF; 32]); + let evidence = raw_value_measurement("cca.rpv", &[0xAA; 32]); + + assert_eq!( + profile.match_measurement(&reference, &evidence, &MatchContext::new()), + Some(false) + ); +} + +#[test] +fn realm_match_rejects_rim_extra_mval_field() { + let profile = CcaRealmProfile::new(); + let mut reference = measurement_with_mkey("cca.rim", &[0xAA; 32]); + reference.mval.raw_value = Some(RawValueChoice::Bytes(vec![0xCC; 32])); + let evidence = measurement_with_mkey("cca.rim", &[0xAA; 32]); + + assert_eq!( + profile.match_measurement(&reference, &evidence, &MatchContext::new()), + Some(false) + ); +} + +#[test] +fn realm_match_defers_for_non_cca_mkey() { + let profile = CcaRealmProfile::new(); + let reference = measurement_with_mkey("tee.something", &[0x11, 0x22, 0x33]); + let evidence = measurement_with_mkey("tee.something", &[0x11, 0x22, 0x33]); + + assert_eq!( + profile.match_measurement(&reference, &evidence, &MatchContext::new()), + None + ); +} + +#[test] +fn realm_match_rejects_authorized_by() { + let profile = CcaRealmProfile::new(); + let reference = measurement_with_mkey("cca.rim", &[0x11, 0x22, 0x33]); + let mut evidence = measurement_with_mkey("cca.rim", &[0x11, 0x22, 0x33]); + evidence.authorized_by = Some(vec![CryptoKey::Bytes(vec![0xCC; 32])]); + + assert_eq!( + profile.match_measurement(&reference, &evidence, &MatchContext::new()), + Some(false) + ); +} + +#[test] +fn diagnosis_does_not_treat_mkeys_as_mval_extensions() { + let profile = CcaPlatformProfile::new(); + assert_eq!( + profile.diagnose_mval_entry( + -999, + &corim::cbor::value::Value::Text("cca.software-component".into()) + ), + None + ); +} diff --git a/corim/tests/profile_validation_tests.rs b/corim/tests/profile_validation_tests.rs index 3e1d6df..afc58d2 100644 --- a/corim/tests/profile_validation_tests.rs +++ b/corim/tests/profile_validation_tests.rs @@ -74,6 +74,27 @@ impl Profile for AlwaysRejectProfile { } } +/// Profile that accepts every measurement pair but rejects every evidence claim. +struct RejectEvidenceProfile { + id: ProfileChoice, +} +impl Profile for RejectEvidenceProfile { + fn identifier(&self) -> &ProfileChoice { + &self.id + } + fn evidence_claim_valid(&self, _claim: &EvidenceClaim) -> bool { + false + } + fn match_measurement( + &self, + _reference: &MeasurementMap, + _evidence: &MeasurementMap, + _ctx: &MatchContext, + ) -> Option { + Some(true) + } +} + fn test_id() -> ProfileChoice { ProfileChoice::Uri("urn:example:test-profile".into()) } @@ -363,3 +384,25 @@ fn endorsement_series_always_reject_profile_blocks_endorsement() { apply_endorsement_series_with_profile(&triples, &evidence, Some(&profile), &ctx()).unwrap(); assert_eq!(with_profile.len(), 0, "profile should block endorsement"); } + +#[test] +fn endorsement_series_rejects_profile_invalid_evidence_claim() { + let profile = RejectEvidenceProfile { id: test_id() }; + let triples = vec![build_series_triple(0xAA, 0xCC)]; + let evidence = vec![EvidenceClaim { + environment: EnvironmentMap::for_class("ACME", "Widget"), + measurements: vec![MeasurementMap { + mkey: Some(MeasuredElement::Text("k".into())), + mval: MeasurementValuesMap { + digests: Some(vec![Digest::new(7, vec![0xBB; 32])]), + ..MeasurementValuesMap::default() + }, + authorized_by: None, + }], + }]; + + let endorsed = + apply_endorsement_series_with_profile(&triples, &evidence, Some(&profile), &ctx()).unwrap(); + + assert!(endorsed.is_empty()); +}