From 437a672ca4b2eacb15630780188b7a6569c90c61 Mon Sep 17 00:00:00 2001 From: ok-nick Date: Thu, 20 Nov 2025 09:34:20 -0500 Subject: [PATCH 1/2] fix: Allow untrusted failure code to be a Valid validation state --- sdk/src/validation_results.rs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/sdk/src/validation_results.rs b/sdk/src/validation_results.rs index f66a341fa..fb06b9ba8 100644 --- a/sdk/src/validation_results.rs +++ b/sdk/src/validation_results.rs @@ -216,17 +216,28 @@ impl ValidationResults { .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()) - }); + let ingredient_failure: Vec<&ValidationStatus> = self + .ingredient_deltas + .as_ref() + .map(|deltas| { + deltas + .iter() + .flat_map(|idv| idv.validation_deltas().failure().iter()) + .collect() + }) + .unwrap_or_default(); // 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() - && !ingredient_failure; + && (failure_codes.is_empty() + || failure_codes.iter().all(|status| { + status.code() == validation_status::SIGNING_CREDENTIAL_UNTRUSTED + })) + && (ingredient_failure.is_empty() + || ingredient_failure.iter().all(|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) From 9fa5071b45a290cc5513b34dd6050d634b91d54a Mon Sep 17 00:00:00 2001 From: ok-nick Date: Thu, 20 Nov 2025 10:03:47 -0500 Subject: [PATCH 2/2] fix: Apply same restrictions on ingredient deltas as active manifest for validation state --- sdk/src/validation_results.rs | 76 +++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 31 deletions(-) diff --git a/sdk/src/validation_results.rs b/sdk/src/validation_results.rs index fb06b9ba8..341f47e0e 100644 --- a/sdk/src/validation_results.rs +++ b/sdk/src/validation_results.rs @@ -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}; @@ -210,39 +208,55 @@ 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 - .success() - .iter() - .map(|status| status.code()) - .collect(); - let failure_codes = active_manifest.failure(); - let ingredient_failure: Vec<&ValidationStatus> = self - .ingredient_deltas - .as_ref() - .map(|deltas| { - deltas - .iter() - .flat_map(|idv| idv.validation_deltas().failure().iter()) - .collect() - }) - .unwrap_or_default(); + fn is_manifest_valid( + success_codes: &[ValidationStatus], + failure_codes: &[ValidationStatus], + ) -> bool { + success_codes + .iter() + .any(|status| status.code() == validation_status::CLAIM_SIGNATURE_VALIDATED) + && success_codes.iter().any(|status| { + status.code() == validation_status::CLAIM_SIGNATURE_INSIDE_VALIDITY + }) + && (failure_codes.is_empty() + || failure_codes.iter().all(|status| { + status.code() == validation_status::SIGNING_CREDENTIAL_UNTRUSTED + })) + } + + fn is_manifest_trusted( + success_codes: &[ValidationStatus], + failure_codes: &[ValidationStatus], + ) -> bool { + success_codes + .iter() + .any(|status| status.code() == validation_status::SIGNING_CREDENTIAL_TRUSTED) + && failure_codes.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| { - status.code() == validation_status::SIGNING_CREDENTIAL_UNTRUSTED - })) - && (ingredient_failure.is_empty() - || ingredient_failure.iter().all(|status| { - status.code() == validation_status::SIGNING_CREDENTIAL_UNTRUSTED - })); + let is_valid = is_manifest_valid(active_manifest.success(), active_manifest.failure()) + && self.ingredient_deltas.as_ref().iter().all(|deltas| { + deltas.iter().all(|idv| { + let deltas = idv.validation_deltas(); + deltas.informational().iter().any(|status| { + status.code() == validation_status::INGREDIENT_PROVENANCE_UNKNOWN + }) || is_manifest_valid(deltas.success(), deltas.failure()) + }) + }); // 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() - && is_valid; + let is_trusted = + is_manifest_trusted(active_manifest.success(), active_manifest.failure()) + && self.ingredient_deltas.as_ref().iter().all(|deltas| { + deltas.iter().all(|idv| { + let deltas = idv.validation_deltas(); + deltas.informational().iter().any(|status| { + status.code() == validation_status::INGREDIENT_PROVENANCE_UNKNOWN + }) || is_manifest_trusted(deltas.success(), deltas.failure()) + }) + }) + && is_valid; if is_trusted { return ValidationState::Trusted;