use crate::cbor;
use crate::error::BuilderError;
#[allow(unused_imports)]
use crate::nostd_prelude::*;
use crate::types::comid::ComidTag;
use crate::types::common::{
CborTime, CryptoKey, EntityMap, LinkedTagMap, TagIdChoice, TagIdentity, ValidityMap,
};
use crate::types::corim::{
ConciseTagChoice, ConciseTlTag, CorimId, CorimLocator, CorimMap, ProfileChoice,
};
use crate::types::coswid::ConciseSwidTag;
use crate::types::environment::EnvironmentMap;
use crate::types::measurement::MeasurementMap;
use crate::types::tags::TAG_CORIM;
use crate::types::triples::{
AttestKeyTriple, CesCondition, ConditionalEndorsementSeriesTriple,
ConditionalEndorsementTriple, ConditionalSeriesRecord, CoswidTriple, DomainDependencyTriple,
DomainMembershipTriple, EndorsedTriple, IdentityTriple, KeyTripleConditions, ReferenceTriple,
StatefulEnvironmentRecord, TriplesMap,
};
use crate::Validate;
use core::sync::atomic::{AtomicUsize, Ordering};
static NEXT_BUILDER_ID: AtomicUsize = AtomicUsize::new(1);
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EnvRef {
builder_id: usize,
label: String,
uid: u32,
}
impl EnvRef {
pub fn label(&self) -> &str {
&self.label
}
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum EnvSpec {
Inline(EnvironmentMap),
Ref(EnvRef),
}
impl From<EnvironmentMap> for EnvSpec {
fn from(env: EnvironmentMap) -> Self {
Self::Inline(env)
}
}
impl From<EnvRef> for EnvSpec {
fn from(r: EnvRef) -> Self {
Self::Ref(r)
}
}
impl From<&EnvRef> for EnvSpec {
fn from(r: &EnvRef) -> Self {
Self::Ref(r.clone())
}
}
#[must_use]
pub struct ComidBuilder {
tag_id: TagIdChoice,
tag_version: Option<u64>,
language: Option<String>,
entities: Option<Vec<EntityMap>>,
linked_tags: Option<Vec<LinkedTagMap>>,
reference_triples: Option<Vec<ReferenceTriple>>,
endorsed_triples: Option<Vec<EndorsedTriple>>,
identity_triples: Option<Vec<IdentityTriple>>,
attest_key_triples: Option<Vec<AttestKeyTriple>>,
dependency_triples: Option<Vec<DomainDependencyTriple>>,
membership_triples: Option<Vec<DomainMembershipTriple>>,
coswid_triples: Option<Vec<CoswidTriple>>,
conditional_endorsement_series: Option<Vec<ConditionalEndorsementSeriesTriple>>,
conditional_endorsement: Option<Vec<ConditionalEndorsementTriple>>,
strict_links: bool,
builder_id: usize,
env_catalog: BTreeMap<String, (u32, EnvironmentMap)>,
next_env_uid: u32,
pending_reference: Vec<(EnvSpec, Vec<MeasurementMap>)>,
pending_endorsed: Vec<(EnvSpec, Vec<MeasurementMap>)>,
pending_identity: Vec<(EnvSpec, Vec<CryptoKey>, Option<KeyTripleConditions>)>,
pending_attest_key: Vec<(EnvSpec, Vec<CryptoKey>, Option<KeyTripleConditions>)>,
pending_dependency: Vec<(EnvSpec, Vec<EnvSpec>)>,
pending_membership: Vec<(EnvSpec, Vec<EnvSpec>)>,
pending_coswid: Vec<(EnvSpec, Vec<TagIdChoice>)>,
#[allow(clippy::type_complexity)]
pending_ces: Vec<(
EnvSpec,
Vec<MeasurementMap>,
Option<Vec<CryptoKey>>,
Vec<ConditionalSeriesRecord>,
)>,
#[allow(clippy::type_complexity)]
pending_ce: Vec<(Vec<(EnvSpec, Vec<MeasurementMap>)>, Vec<EndorsedTriple>)>,
}
impl ComidBuilder {
pub fn new(tag_id: TagIdChoice) -> Self {
Self {
tag_id,
tag_version: None,
language: None,
entities: None,
linked_tags: None,
reference_triples: None,
endorsed_triples: None,
identity_triples: None,
attest_key_triples: None,
dependency_triples: None,
membership_triples: None,
coswid_triples: None,
conditional_endorsement_series: None,
conditional_endorsement: None,
strict_links: false,
builder_id: NEXT_BUILDER_ID.fetch_add(1, Ordering::Relaxed),
env_catalog: BTreeMap::new(),
next_env_uid: 0,
pending_reference: Vec::new(),
pending_endorsed: Vec::new(),
pending_identity: Vec::new(),
pending_attest_key: Vec::new(),
pending_dependency: Vec::new(),
pending_membership: Vec::new(),
pending_coswid: Vec::new(),
pending_ces: Vec::new(),
pending_ce: Vec::new(),
}
}
pub fn strict_links(mut self, enable: bool) -> Self {
self.strict_links = enable;
self
}
pub fn declare_env(
&mut self,
label: impl Into<String>,
env: EnvironmentMap,
) -> Result<EnvRef, BuilderError> {
let label = label.into();
if self.env_catalog.contains_key(&label) {
return Err(BuilderError::DuplicateEnvLabel { label });
}
let uid = self.next_env_uid;
self.next_env_uid = self
.next_env_uid
.checked_add(1)
.ok_or(BuilderError::Validation(
"env-catalog uid counter overflow".into(),
))?;
self.env_catalog.insert(label.clone(), (uid, env));
Ok(EnvRef {
builder_id: self.builder_id,
label,
uid,
})
}
pub fn add_reference_triple_for(
mut self,
env: impl Into<EnvSpec>,
measurements: Vec<MeasurementMap>,
) -> Self {
self.pending_reference.push((env.into(), measurements));
self
}
pub fn add_endorsed_triple_for(
mut self,
env: impl Into<EnvSpec>,
endorsement: Vec<MeasurementMap>,
) -> Self {
self.pending_endorsed.push((env.into(), endorsement));
self
}
pub fn add_identity_triple_for(
mut self,
env: impl Into<EnvSpec>,
keys: Vec<CryptoKey>,
conditions: Option<KeyTripleConditions>,
) -> Self {
self.pending_identity.push((env.into(), keys, conditions));
self
}
pub fn add_attest_key_triple_for(
mut self,
env: impl Into<EnvSpec>,
keys: Vec<CryptoKey>,
conditions: Option<KeyTripleConditions>,
) -> Self {
self.pending_attest_key.push((env.into(), keys, conditions));
self
}
pub fn add_dependency_triple_for(
mut self,
domain: impl Into<EnvSpec>,
trustees: Vec<EnvSpec>,
) -> Self {
self.pending_dependency.push((domain.into(), trustees));
self
}
pub fn add_membership_triple_for(
mut self,
domain: impl Into<EnvSpec>,
members: Vec<EnvSpec>,
) -> Self {
self.pending_membership.push((domain.into(), members));
self
}
pub fn add_coswid_triple_for(
mut self,
env: impl Into<EnvSpec>,
tag_ids: Vec<TagIdChoice>,
) -> Self {
self.pending_coswid.push((env.into(), tag_ids));
self
}
pub fn add_conditional_endorsement_series_for(
mut self,
condition_env: impl Into<EnvSpec>,
condition_claims_list: Vec<MeasurementMap>,
condition_authorized_by: Option<Vec<CryptoKey>>,
series: Vec<ConditionalSeriesRecord>,
) -> Self {
self.pending_ces.push((
condition_env.into(),
condition_claims_list,
condition_authorized_by,
series,
));
self
}
pub fn add_conditional_endorsement_for(
mut self,
conditions: Vec<(EnvSpec, Vec<MeasurementMap>)>,
endorsements: Vec<EndorsedTriple>,
) -> Self {
self.pending_ce.push((conditions, endorsements));
self
}
fn resolve(&self, spec: EnvSpec) -> Result<EnvironmentMap, BuilderError> {
match spec {
EnvSpec::Inline(env) => Ok(env),
EnvSpec::Ref(r) => {
if r.builder_id != self.builder_id {
return Err(BuilderError::RefFromOtherBuilder { label: r.label });
}
let (_uid, env) = self
.env_catalog
.get(&r.label)
.expect("EnvRef invariant: catalog entry present");
Ok(env.clone())
}
}
}
pub fn set_tag_version(mut self, version: u64) -> Self {
self.tag_version = Some(version);
self
}
pub fn set_language(mut self, lang: impl Into<String>) -> Self {
self.language = Some(lang.into());
self
}
pub fn add_entity(mut self, entity: EntityMap) -> Self {
self.entities.get_or_insert_with(Vec::new).push(entity);
self
}
pub fn add_linked_tag(mut self, linked_tag: LinkedTagMap) -> Self {
self.linked_tags
.get_or_insert_with(Vec::new)
.push(linked_tag);
self
}
pub fn add_reference_triple(mut self, triple: ReferenceTriple) -> Self {
self.reference_triples
.get_or_insert_with(Vec::new)
.push(triple);
self
}
pub fn add_endorsed_triple(mut self, triple: EndorsedTriple) -> Self {
self.endorsed_triples
.get_or_insert_with(Vec::new)
.push(triple);
self
}
pub fn add_identity_triple(mut self, triple: IdentityTriple) -> Self {
self.identity_triples
.get_or_insert_with(Vec::new)
.push(triple);
self
}
pub fn add_attest_key_triple(mut self, triple: AttestKeyTriple) -> Self {
self.attest_key_triples
.get_or_insert_with(Vec::new)
.push(triple);
self
}
pub fn add_dependency_triple(mut self, triple: DomainDependencyTriple) -> Self {
self.dependency_triples
.get_or_insert_with(Vec::new)
.push(triple);
self
}
pub fn add_membership_triple(mut self, triple: DomainMembershipTriple) -> Self {
self.membership_triples
.get_or_insert_with(Vec::new)
.push(triple);
self
}
pub fn add_coswid_triple(mut self, triple: CoswidTriple) -> Self {
self.coswid_triples
.get_or_insert_with(Vec::new)
.push(triple);
self
}
pub fn add_conditional_endorsement_series(
mut self,
triple: ConditionalEndorsementSeriesTriple,
) -> Self {
self.conditional_endorsement_series
.get_or_insert_with(Vec::new)
.push(triple);
self
}
pub fn add_conditional_endorsement(mut self, triple: ConditionalEndorsementTriple) -> Self {
self.conditional_endorsement
.get_or_insert_with(Vec::new)
.push(triple);
self
}
pub fn build(mut self) -> Result<ComidTag, BuilderError> {
for (env_spec, measurements) in core::mem::take(&mut self.pending_reference) {
let env = self.resolve(env_spec)?;
self.reference_triples
.get_or_insert_with(Vec::new)
.push(ReferenceTriple::new(env, measurements));
}
for (env_spec, endorsement) in core::mem::take(&mut self.pending_endorsed) {
let env = self.resolve(env_spec)?;
self.endorsed_triples
.get_or_insert_with(Vec::new)
.push(EndorsedTriple::new(env, endorsement));
}
for (env_spec, keys, conditions) in core::mem::take(&mut self.pending_identity) {
let env = self.resolve(env_spec)?;
self.identity_triples
.get_or_insert_with(Vec::new)
.push(IdentityTriple::new(env, keys, conditions));
}
for (env_spec, keys, conditions) in core::mem::take(&mut self.pending_attest_key) {
let env = self.resolve(env_spec)?;
self.attest_key_triples
.get_or_insert_with(Vec::new)
.push(AttestKeyTriple::new(env, keys, conditions));
}
for (domain_spec, trustees_spec) in core::mem::take(&mut self.pending_dependency) {
let domain = self.resolve(domain_spec)?;
let mut trustees = Vec::with_capacity(trustees_spec.len());
for t in trustees_spec {
trustees.push(self.resolve(t)?);
}
self.dependency_triples
.get_or_insert_with(Vec::new)
.push(DomainDependencyTriple::new(domain, trustees));
}
for (domain_spec, members_spec) in core::mem::take(&mut self.pending_membership) {
let domain = self.resolve(domain_spec)?;
let mut members = Vec::with_capacity(members_spec.len());
for m in members_spec {
members.push(self.resolve(m)?);
}
self.membership_triples
.get_or_insert_with(Vec::new)
.push(DomainMembershipTriple::new(domain, members));
}
for (env_spec, tag_ids) in core::mem::take(&mut self.pending_coswid) {
let env = self.resolve(env_spec)?;
self.coswid_triples
.get_or_insert_with(Vec::new)
.push(CoswidTriple::new(env, tag_ids));
}
for (env_spec, claims_list, authorized_by, series) in core::mem::take(&mut self.pending_ces)
{
let environment = self.resolve(env_spec)?;
self.conditional_endorsement_series
.get_or_insert_with(Vec::new)
.push(ConditionalEndorsementSeriesTriple::new(
CesCondition {
environment,
claims_list,
authorized_by,
},
series,
));
}
for (conditions_spec, endorsements) in core::mem::take(&mut self.pending_ce) {
let mut conditions = Vec::with_capacity(conditions_spec.len());
for (env_spec, measurements) in conditions_spec {
let env = self.resolve(env_spec)?;
conditions.push(StatefulEnvironmentRecord(env, measurements));
}
self.conditional_endorsement
.get_or_insert_with(Vec::new)
.push(ConditionalEndorsementTriple(conditions, endorsements));
}
let has_triples = self.reference_triples.is_some()
|| self.endorsed_triples.is_some()
|| self.identity_triples.is_some()
|| self.attest_key_triples.is_some()
|| self.dependency_triples.is_some()
|| self.membership_triples.is_some()
|| self.coswid_triples.is_some()
|| self.conditional_endorsement_series.is_some()
|| self.conditional_endorsement.is_some();
if !has_triples {
return Err(BuilderError::EmptyTriples);
}
if let Some(ref triples) = self.reference_triples {
for t in triples {
if t.1.is_empty() {
return Err(BuilderError::EmptyList {
field: "ref-claims",
});
}
}
}
if let Some(ref triples) = self.endorsed_triples {
for t in triples {
if t.1.is_empty() {
return Err(BuilderError::EmptyList {
field: "endorsement",
});
}
}
}
if let Some(ref triples) = self.identity_triples {
for t in triples {
if t.1.is_empty() {
return Err(BuilderError::EmptyList { field: "key-list" });
}
}
}
if let Some(ref triples) = self.attest_key_triples {
for t in triples {
if t.1.is_empty() {
return Err(BuilderError::EmptyList { field: "key-list" });
}
}
}
if let Some(ref triples) = self.dependency_triples {
for t in triples {
if t.1.is_empty() {
return Err(BuilderError::EmptyList { field: "trustees" });
}
}
}
if let Some(ref triples) = self.membership_triples {
for t in triples {
if t.1.is_empty() {
return Err(BuilderError::EmptyList { field: "members" });
}
}
}
if let Some(ref triples) = self.coswid_triples {
for t in triples {
if t.1.is_empty() {
return Err(BuilderError::EmptyList { field: "tag-ids" });
}
}
}
if self.strict_links {
let anchors: Vec<&EnvironmentMap> = self
.reference_triples
.as_deref()
.unwrap_or(&[])
.iter()
.map(|t| &t.0)
.collect();
let is_anchored = |env: &EnvironmentMap| anchors.contains(&env);
let ref_triples = self.reference_triples.as_deref().unwrap_or(&[]);
let meas_anchors = |env: &EnvironmentMap| -> Vec<&MeasurementMap> {
ref_triples
.iter()
.filter(|t| &t.0 == env)
.flat_map(|t| t.1.iter())
.collect()
};
let check_meas = |env: &EnvironmentMap,
to_anchor: &[MeasurementMap],
triple_kind: &'static str,
triple_index: usize|
-> Result<(), BuilderError> {
if to_anchor.is_empty() {
return Ok(());
}
let pool = meas_anchors(env);
for (mi, m) in to_anchor.iter().enumerate() {
if !pool.contains(&m) {
return Err(BuilderError::UnanchoredConditionMeasurement {
triple_kind,
triple_index,
measurement_index: mi,
});
}
}
Ok(())
};
if let Some(ref triples) = self.conditional_endorsement_series {
for (i, t) in triples.iter().enumerate() {
if !is_anchored(&t.0.environment) {
return Err(BuilderError::UnanchoredConditionEnv {
triple_kind: "conditional-endorsement-series",
index: i,
});
}
check_meas(
&t.0.environment,
&t.0.claims_list,
"conditional-endorsement-series",
i,
)?;
for series in &t.1 {
check_meas(
&t.0.environment,
&series.0,
"conditional-endorsement-series-selection",
i,
)?;
}
}
}
if let Some(ref triples) = self.endorsed_triples {
for (i, t) in triples.iter().enumerate() {
if !is_anchored(&t.0) {
return Err(BuilderError::UnanchoredConditionEnv {
triple_kind: "endorsed",
index: i,
});
}
}
}
if let Some(ref triples) = self.conditional_endorsement {
for (i, t) in triples.iter().enumerate() {
for stateful in &t.0 {
if !is_anchored(&stateful.0) {
return Err(BuilderError::UnanchoredConditionEnv {
triple_kind: "conditional-endorsement",
index: i,
});
}
check_meas(&stateful.0, &stateful.1, "conditional-endorsement", i)?;
}
}
}
}
let triples = TriplesMap {
reference_triples: self.reference_triples,
endorsed_triples: self.endorsed_triples,
identity_triples: self.identity_triples,
attest_key_triples: self.attest_key_triples,
dependency_triples: self.dependency_triples,
membership_triples: self.membership_triples,
coswid_triples: self.coswid_triples,
conditional_endorsement_series: self.conditional_endorsement_series,
conditional_endorsement: self.conditional_endorsement,
};
Ok(ComidTag {
language: self.language,
tag_identity: TagIdentity {
tag_id: self.tag_id,
tag_version: self.tag_version,
},
entities: self.entities,
linked_tags: self.linked_tags,
triples,
})
}
}
#[must_use]
pub struct CotlBuilder {
tag_id: TagIdChoice,
tag_version: Option<u64>,
tags_list: Vec<TagIdentity>,
not_before: Option<i64>,
not_after: i64,
}
impl CotlBuilder {
pub fn new(tag_id: TagIdChoice, not_after: i64) -> Self {
Self {
tag_id,
tag_version: None,
tags_list: Vec::new(),
not_before: None,
not_after,
}
}
pub fn set_tag_version(mut self, version: u64) -> Self {
self.tag_version = Some(version);
self
}
pub fn set_not_before(mut self, not_before: i64) -> Self {
self.not_before = Some(not_before);
self
}
pub fn add_tag(mut self, tag_identity: TagIdentity) -> Self {
self.tags_list.push(tag_identity);
self
}
pub fn add_tag_id(mut self, tag_id: TagIdChoice) -> Self {
self.tags_list.push(TagIdentity {
tag_id,
tag_version: None,
});
self
}
pub fn build(self) -> Result<ConciseTlTag, BuilderError> {
if self.tags_list.is_empty() {
return Err(BuilderError::EmptyList { field: "tags-list" });
}
if let Some(nb) = self.not_before {
if nb > self.not_after {
return Err(BuilderError::InvalidValidity);
}
}
Ok(ConciseTlTag {
tag_identity: TagIdentity {
tag_id: self.tag_id,
tag_version: self.tag_version,
},
tags_list: self.tags_list,
tl_validity: ValidityMap {
not_before: self.not_before.map(CborTime::new),
not_after: CborTime::new(self.not_after),
},
})
}
}
#[must_use]
pub struct CorimBuilder {
id: CorimId,
profile: Option<ProfileChoice>,
rim_validity: Option<ValidityMap>,
entities: Option<Vec<EntityMap>>,
dependent_rims: Option<Vec<CorimLocator>>,
tags: Vec<ConciseTagChoice>,
}
impl CorimBuilder {
pub fn new(id: CorimId) -> Self {
Self {
id,
profile: None,
rim_validity: None,
entities: None,
dependent_rims: None,
tags: Vec::new(),
}
}
pub fn set_profile(mut self, profile: ProfileChoice) -> Self {
self.profile = Some(profile);
self
}
pub fn set_validity(
mut self,
not_before: Option<i64>,
not_after: i64,
) -> Result<Self, BuilderError> {
if let Some(nb) = not_before {
if nb > not_after {
return Err(BuilderError::InvalidValidity);
}
}
self.rim_validity = Some(ValidityMap {
not_before: not_before.map(CborTime::new),
not_after: CborTime::new(not_after),
});
Ok(self)
}
pub fn add_entity(mut self, entity: EntityMap) -> Self {
self.entities.get_or_insert_with(Vec::new).push(entity);
self
}
pub fn add_dependent_rim(mut self, locator: CorimLocator) -> Self {
self.dependent_rims
.get_or_insert_with(Vec::new)
.push(locator);
self
}
pub fn add_comid_tag(mut self, comid: ComidTag) -> Result<Self, BuilderError> {
let comid_bytes = cbor::encode(&comid)?;
self.tags.push(ConciseTagChoice::Comid(comid_bytes));
Ok(self)
}
pub fn add_coswid_tag(mut self, coswid_bytes: Vec<u8>) -> Self {
self.tags.push(ConciseTagChoice::Coswid(coswid_bytes));
self
}
pub fn add_coswid(mut self, coswid: ConciseSwidTag) -> Result<Self, BuilderError> {
coswid
.valid()
.map_err(|e: String| BuilderError::Validation(e))?;
let coswid_bytes = cbor::encode(&coswid)?;
self.tags.push(ConciseTagChoice::Coswid(coswid_bytes));
Ok(self)
}
pub fn add_cotl_tag(mut self, cotl_bytes: Vec<u8>) -> Self {
self.tags.push(ConciseTagChoice::Cotl(cotl_bytes));
self
}
pub fn add_cotl(mut self, cotl: ConciseTlTag) -> Result<Self, BuilderError> {
let cotl_bytes = cbor::encode(&cotl)?;
self.tags.push(ConciseTagChoice::Cotl(cotl_bytes));
Ok(self)
}
pub fn add_tag(mut self, tag: ConciseTagChoice) -> Self {
self.tags.push(tag);
self
}
pub fn build(self) -> Result<CorimMap, BuilderError> {
if self.tags.is_empty() {
return Err(BuilderError::NoTags);
}
Ok(CorimMap {
id: self.id,
tags: self.tags,
dependent_rims: self.dependent_rims,
profile: self.profile,
rim_validity: self.rim_validity,
entities: self.entities,
})
}
pub fn build_bytes(self) -> Result<Vec<u8>, BuilderError> {
let corim = self.build()?;
let tagged = cbor::value::Tagged::new(TAG_CORIM, corim);
let bytes = cbor::encode(&tagged)?;
Ok(bytes)
}
}