use std::collections::HashMap;
use crate::c2pa_core::{ComplianceLevel, EngineProfile, OperatingMode, SpecVersion};
use crate::c2pa_validate::{verify_identity_with_caller_verified_binding, CawgTrustInputs};
use crate::{
copy_status, map_validate_error, Error, ResolvedOptions, ValidationResults, VerifyOptions,
MAX_MANIFEST_STORE_BYTES,
};
const SPEC_VERSIONS: [&str; 6] = ["1.4", "2.0", "2.1", "2.2", "2.3", "2.4"];
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CawgProfile {
profile: EngineProfile,
}
impl CawgProfile {
pub fn new(
spec_version: &str,
conformance_mode: bool,
compliance_program: bool,
) -> Result<Self, Error> {
let version = SPEC_VERSIONS
.contains(&spec_version)
.then(|| SpecVersion::from_str(spec_version))
.flatten()
.ok_or_else(|| {
Error::Verification(format!(
"spec_version {spec_version:?} is not one of {}",
SPEC_VERSIONS.join(", ")
))
})?;
let mode = if conformance_mode {
OperatingMode::Conformance
} else {
OperatingMode::Regular
};
let compliance = if compliance_program {
ComplianceLevel::ConformanceProgram
} else {
ComplianceLevel::CoreSpec
};
Ok(Self {
profile: EngineProfile::new(version, mode).with_compliance(compliance),
})
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CawgStoreHost {
Asset,
HostLess,
}
#[non_exhaustive]
#[derive(Debug, Clone)]
pub enum CawgEvaluation {
#[non_exhaustive]
Evaluated {
manifest_label: String,
store_manifest_sha256: Vec<(String, [u8; 32])>,
validation_results: ValidationResults,
},
#[non_exhaustive]
StoreGateClosed {
manifest_label: Option<String>,
code: String,
},
}
pub struct CawgEvaluator {
resolved: ResolvedOptions,
did_documents: Option<HashMap<String, serde_json::Value>>,
ica_trusted_issuers: Option<Vec<String>>,
ica_trust_anchors: Option<Vec<String>>,
ica_status_lists: Option<HashMap<String, String>>,
}
impl std::fmt::Debug for CawgEvaluator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CawgEvaluator")
.field("profile", &self.resolved.profile)
.finish_non_exhaustive()
}
}
impl CawgEvaluator {
pub fn new(options: &VerifyOptions, profile: &CawgProfile) -> Result<Self, Error> {
if options.strict_conformance {
return Err(Error::Verification(
"strict_conformance must be false: the evaluator takes its posture from CawgProfile"
.into(),
));
}
if options.online == Some(true) {
return Err(Error::Verification(
"online must not be Some(true): the evaluator never fetches".into(),
));
}
if options
.external_data
.as_ref()
.is_some_and(|data| !data.is_empty())
{
return Err(Error::Verification(
"external_data must be empty: the evaluator never reads external content".into(),
));
}
if options.validation_time.is_none() {
return Err(Error::Verification(
"validation_time is required: the evaluator judges every evaluation at one fixed instant"
.into(),
));
}
let mut resolved = ResolvedOptions::resolve(options)?;
resolved.profile = profile.profile;
Ok(Self {
resolved,
did_documents: options.cawg_did_documents.clone(),
ica_trusted_issuers: options.cawg_ica_trusted_issuers.clone(),
ica_trust_anchors: options.cawg_ica_trust_anchors.clone(),
ica_status_lists: options.cawg_ica_status_lists.clone(),
})
}
pub fn evaluate_with_caller_verified_binding(
&self,
manifest_store: &[u8],
host: CawgStoreHost,
) -> Result<CawgEvaluation, Error> {
if manifest_store.len() > MAX_MANIFEST_STORE_BYTES {
return Err(Error::Verification(format!(
"manifest store exceeds the {MAX_MANIFEST_STORE_BYTES} byte limit"
)));
}
let input = self.resolved.input(&[], "");
let cawg_inputs = CawgTrustInputs::new(
self.resolved.cawg_trust(),
self.resolved.cawg_allowed_certs(),
self.resolved.cawg_document_signing_require_anchor,
self.did_documents.as_ref(),
self.ica_trusted_issuers.as_deref(),
self.ica_trust_anchors.as_deref(),
self.ica_status_lists.as_ref(),
);
let output = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
verify_identity_with_caller_verified_binding(
manifest_store,
host == CawgStoreHost::HostLess,
&input,
cawg_inputs,
)
}))
.map_err(|_| Error::Verification("CAWG evaluation panicked".into()))?
.map_err(map_validate_error)?;
Ok(match (output.identity, output.manifest_label) {
(Some(identity), Some(manifest_label)) => CawgEvaluation::Evaluated {
manifest_label,
store_manifest_sha256: output.store_manifest_sha256,
validation_results: ValidationResults {
success: identity.success.iter().map(copy_status).collect(),
informational: identity.informational.iter().map(copy_status).collect(),
failure: identity.failure.iter().map(copy_status).collect(),
},
},
(_, manifest_label) => CawgEvaluation::StoreGateClosed {
manifest_label,
code: output.gate_code.ok_or_else(|| {
Error::Verification(
"the identity step neither ran nor recorded a gate status".into(),
)
})?,
},
})
}
}