use super::super::super::*;
use super::{
super::{
digest::deployment_root_verification_report_digest,
error::DeploymentRootVerificationReportError,
},
checks::{present_value, root_observation_source_label_from_source},
shared::root_verification_transition,
};
pub fn validate_deployment_root_verification_report(
report: &DeploymentRootVerificationReportV1,
) -> Result<(), DeploymentRootVerificationReportError> {
if report.schema_version != DEPLOYMENT_TRUTH_SCHEMA_VERSION {
return Err(
DeploymentRootVerificationReportError::SchemaVersionMismatch {
expected: DEPLOYMENT_TRUTH_SCHEMA_VERSION,
actual: report.schema_version,
},
);
}
ensure_root_verification_field("report_id", report.report_id.as_str())?;
ensure_root_verification_sha256("report_digest", report.report_digest.as_str())?;
ensure_root_verification_field("requested_at", report.requested_at.as_str())?;
ensure_root_verification_field("deployment_name", report.deployment_name.as_str())?;
ensure_root_verification_field("network", report.network.as_str())?;
ensure_root_verification_field(
"expected_fleet_template",
report.expected_fleet_template.as_str(),
)?;
ensure_root_verification_field(
"expected_root_principal",
report.expected_root_principal.as_str(),
)?;
ensure_root_verification_field("source_check_id", report.source_check_id.as_str())?;
ensure_root_verification_sha256("source_check_digest", report.source_check_digest.as_str())?;
ensure_root_verification_field(
"source_deployment_plan_id",
report.source_deployment_plan_id.as_str(),
)?;
ensure_root_verification_sha256(
"source_deployment_plan_digest",
report.source_deployment_plan_digest.as_str(),
)?;
ensure_root_verification_field("source_inventory_id", report.source_inventory_id.as_str())?;
ensure_root_verification_sha256(
"source_inventory_digest",
report.source_inventory_digest.as_str(),
)?;
if report.evidence_status != report_evidence_status(report)
|| report.state_transition != report_state_transition(report)
{
return Err(DeploymentRootVerificationReportError::StatusMismatch);
}
ensure_root_verification_report_checks_consistent(report)?;
if report.report_digest != deployment_root_verification_report_digest(report) {
return Err(DeploymentRootVerificationReportError::DigestMismatch {
field: "report_digest",
});
}
Ok(())
}
fn report_evidence_status(
report: &DeploymentRootVerificationReportV1,
) -> DeploymentRootVerificationEvidenceStatusV1 {
if report.blockers.is_empty()
&& report.identity_checks.iter().all(|check| check.satisfied)
&& report.evidence_checks.iter().all(|check| check.satisfied)
{
DeploymentRootVerificationEvidenceStatusV1::EvidenceSatisfied
} else {
DeploymentRootVerificationEvidenceStatusV1::VerificationFailed
}
}
const fn report_state_transition(
report: &DeploymentRootVerificationReportV1,
) -> DeploymentRootVerificationStateTransitionV1 {
root_verification_transition(report.evidence_status, report.current_root_verification)
}
fn ensure_root_verification_report_checks_consistent(
report: &DeploymentRootVerificationReportV1,
) -> Result<(), DeploymentRootVerificationReportError> {
ensure_report_check_names(
&report.identity_checks,
&[
"deployment_name",
"network",
"fleet_template",
"root_principal",
"plan_deployment_name",
"plan_network",
"plan_fleet_template",
],
)?;
ensure_report_check_names(
&report.evidence_checks,
&[
"explicit_observed_root",
"root_observation_source",
"observed_root_canister_id",
"source_check_id",
"source_deployment_plan_id",
"source_inventory_id",
],
)?;
for check in report.identity_checks.iter().chain(&report.evidence_checks) {
if check.satisfied != (check.expected == check.observed) {
return Err(DeploymentRootVerificationReportError::CheckMismatch {
check: check.name.clone(),
});
}
}
ensure_report_check_value(
&report.identity_checks,
"deployment_name",
Some(report.deployment_name.as_str()),
report.observed_deployment_name.as_deref(),
)?;
ensure_report_check_value(
&report.identity_checks,
"network",
Some(report.network.as_str()),
report.observed_network.as_deref(),
)?;
ensure_report_check_value(
&report.identity_checks,
"fleet_template",
Some(report.expected_fleet_template.as_str()),
report.observed_fleet_template.as_deref(),
)?;
ensure_report_check_value(
&report.identity_checks,
"root_principal",
Some(report.expected_root_principal.as_str()),
report.observed_root_principal.as_deref(),
)?;
let observed_root_present = report.observed_deployment_name.is_some()
&& report.observed_network.is_some()
&& report.observed_fleet_template.is_some()
&& report.observed_root_principal.is_some()
&& report.observed_root_canister_id.is_some()
&& report.observed_root_observation_source.is_some();
ensure_report_check_value(
&report.evidence_checks,
"explicit_observed_root",
Some("present"),
observed_root_present.then_some("present"),
)?;
ensure_report_check_value(
&report.evidence_checks,
"root_observation_source",
Some("IcpCanisterStatus"),
report
.observed_root_observation_source
.as_ref()
.map(root_observation_source_label_from_source),
)?;
ensure_report_check_value(
&report.evidence_checks,
"observed_root_canister_id",
Some(report.expected_root_principal.as_str()),
report.observed_root_canister_id.as_deref(),
)?;
ensure_report_check_value(
&report.evidence_checks,
"source_check_id",
Some("present"),
present_value(report.source_check_id.as_str()),
)?;
ensure_report_check_value(
&report.evidence_checks,
"source_deployment_plan_id",
Some("present"),
present_value(report.source_deployment_plan_id.as_str()),
)?;
ensure_report_check_value(
&report.evidence_checks,
"source_inventory_id",
Some("present"),
present_value(report.source_inventory_id.as_str()),
)?;
Ok(())
}
fn ensure_report_check_names(
checks: &[DeploymentRootVerificationCheckV1],
expected: &[&'static str],
) -> Result<(), DeploymentRootVerificationReportError> {
for check in checks {
if !expected.contains(&check.name.as_str()) {
return Err(DeploymentRootVerificationReportError::CheckMismatch {
check: check.name.clone(),
});
}
}
for expected_name in expected {
if checks
.iter()
.filter(|check| check.name == *expected_name)
.count()
!= 1
{
return Err(DeploymentRootVerificationReportError::CheckMismatch {
check: (*expected_name).to_string(),
});
}
}
Ok(())
}
fn ensure_report_check_value(
checks: &[DeploymentRootVerificationCheckV1],
name: &'static str,
expected: Option<&str>,
observed: Option<&str>,
) -> Result<(), DeploymentRootVerificationReportError> {
let Some(check) = checks.iter().find(|check| check.name == name) else {
return Err(DeploymentRootVerificationReportError::CheckMismatch {
check: name.to_string(),
});
};
if check.expected.as_deref() == expected
&& check.observed.as_deref() == observed
&& check.satisfied == (expected == observed)
{
Ok(())
} else {
Err(DeploymentRootVerificationReportError::CheckMismatch {
check: name.to_string(),
})
}
}
const fn ensure_root_verification_field(
field: &'static str,
value: &str,
) -> Result<(), DeploymentRootVerificationReportError> {
if value.is_empty() {
Err(DeploymentRootVerificationReportError::MissingRequiredField { field })
} else {
Ok(())
}
}
fn ensure_root_verification_sha256(
field: &'static str,
value: &str,
) -> Result<(), DeploymentRootVerificationReportError> {
if value.is_empty() {
return Err(DeploymentRootVerificationReportError::MissingRequiredField { field });
}
if is_lower_hex_sha256(value) {
Ok(())
} else {
Err(DeploymentRootVerificationReportError::InvalidSha256Digest { field })
}
}
fn is_lower_hex_sha256(value: &str) -> bool {
value.len() == 64
&& value
.bytes()
.all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase())
}