Skip to content
Open
54 changes: 34 additions & 20 deletions sdk/src/validation_results.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@
// specific language governing permissions and limitations under
// each license.

use std::collections::HashSet;

#[cfg(feature = "json_schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -210,30 +208,46 @@ impl ValidationResults {
/// [§14.3. Validation states]: https://spec.c2pa.org/specifications/specifications/2.2/specs/C2PA_Specification.html#_validation_states
pub fn validation_state(&self) -> ValidationState {
if let Some(active_manifest) = self.active_manifest.as_ref() {
let success_codes: HashSet<&str> = active_manifest
// https://spec.c2pa.org/specifications/specifications/2.2/specs/C2PA_Specification.html#_valid_manifest
let is_valid = active_manifest
// First check if the claim is valid and the certificate hasn't expired.
.success()
.iter()
.map(|status| status.code())
.collect();
let failure_codes = active_manifest.failure();
let ingredient_failure = self.ingredient_deltas.as_ref().is_some_and(|deltas| {
deltas
.iter()
.any(|idv| !idv.validation_deltas().failure().is_empty())
});

// https://spec.c2pa.org/specifications/specifications/2.2/specs/C2PA_Specification.html#_valid_manifest
let is_valid = success_codes.contains(validation_status::CLAIM_SIGNATURE_VALIDATED)
&& success_codes.contains(validation_status::CLAIM_SIGNATURE_INSIDE_VALIDITY)
&& (failure_codes.is_empty()
|| failure_codes.iter().all(|status| {
.any(|status| status.code() == validation_status::CLAIM_SIGNATURE_VALIDATED)
&& active_manifest.success().iter().any(|status| {
status.code() == validation_status::CLAIM_SIGNATURE_INSIDE_VALIDITY
})
// Then check if the manifest contains either no failures or that it's only untrusted.
&& (active_manifest.failure().is_empty()
|| active_manifest.failure().iter().all(|status| {
status.code() == validation_status::SIGNING_CREDENTIAL_UNTRUSTED
}))
&& !ingredient_failure;
// Finally check if the ingredients contain either no failures or the only failure is
// that the ingredient is untrusted.
&& self.ingredient_deltas.as_ref().iter().all(|deltas| {
deltas.iter().all(|idv| {
let deltas = idv.validation_deltas();
deltas.failure().is_empty()
|| deltas.failure().iter().any(|status| {
status.code() == validation_status::SIGNING_CREDENTIAL_UNTRUSTED
})
})
});

// https://spec.c2pa.org/specifications/specifications/2.2/specs/C2PA_Specification.html#_trusted_manifest
let is_trusted = success_codes.contains(validation_status::SIGNING_CREDENTIAL_TRUSTED)
&& failure_codes.is_empty()
let is_trusted = active_manifest
// First check if the signing certificate is trusted.
.success()
.iter()
.any(|status| status.code() == validation_status::SIGNING_CREDENTIAL_TRUSTED)
// Then check that there are no errors.
&& active_manifest.failure().is_empty()
// Finally check if the ingredients contain no failures.
&& self.ingredient_deltas.as_ref().iter().all(|deltas| {
deltas.iter().all(|idv| {
idv.validation_deltas().failure().is_empty()
})
})
&& is_valid;

if is_trusted {
Expand Down
Loading