Skip to content
Open
280 changes: 260 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().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)
&& 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 Expand Up @@ -950,3 +964,229 @@ pub mod validation_codes {
}
}
}

#[cfg(test)]
pub mod tests {
use super::*;
use crate::validation_status::{
ASSERTION_DATAHASH_MISMATCH, CLAIM_MALFORMED, CLAIM_SIGNATURE_INSIDE_VALIDITY,
CLAIM_SIGNATURE_VALIDATED, SIGNING_CREDENTIAL_TRUSTED, SIGNING_CREDENTIAL_UNTRUSTED,
};

#[test]
fn trusted_state() {
let mut validation_results = ValidationResults::default();

validation_results.add_status(
ValidationStatus::new(CLAIM_SIGNATURE_VALIDATED).set_kind(LogKind::Success),
);
validation_results.add_status(
ValidationStatus::new(CLAIM_SIGNATURE_INSIDE_VALIDITY).set_kind(LogKind::Success),
);
validation_results.add_status(
ValidationStatus::new(SIGNING_CREDENTIAL_TRUSTED).set_kind(LogKind::Success),
);

assert_eq!(
validation_results.validation_state(),
ValidationState::Trusted
);
}

#[test]
fn not_trusted_state_with_failure() {
let mut validation_results = ValidationResults::default();

validation_results.add_status(
ValidationStatus::new(CLAIM_SIGNATURE_VALIDATED).set_kind(LogKind::Success),
);
validation_results.add_status(
ValidationStatus::new(CLAIM_SIGNATURE_INSIDE_VALIDITY).set_kind(LogKind::Success),
);
validation_results.add_status(
ValidationStatus::new(SIGNING_CREDENTIAL_TRUSTED).set_kind(LogKind::Success),
);

validation_results.add_status(ValidationStatus::new_failure(SIGNING_CREDENTIAL_UNTRUSTED));

assert_eq!(
validation_results.validation_state(),
ValidationState::Valid
);
}

#[test]
fn not_trusted_state_with_failure_delta() {
let mut validation_results = ValidationResults::default();

validation_results.add_status(
ValidationStatus::new(CLAIM_SIGNATURE_VALIDATED).set_kind(LogKind::Success),
);
validation_results.add_status(
ValidationStatus::new(CLAIM_SIGNATURE_INSIDE_VALIDITY).set_kind(LogKind::Success),
);
validation_results.add_status(
ValidationStatus::new(SIGNING_CREDENTIAL_TRUSTED).set_kind(LogKind::Success),
);

validation_results.add_status(
ValidationStatus::new_failure(SIGNING_CREDENTIAL_UNTRUSTED).set_ingredient_uri("1"),
);

assert_eq!(
validation_results.validation_state(),
ValidationState::Valid
);
}

#[test]
fn valid_state() {
let mut validation_results = ValidationResults::default();

validation_results.add_status(
ValidationStatus::new(CLAIM_SIGNATURE_VALIDATED).set_kind(LogKind::Success),
);
validation_results.add_status(
ValidationStatus::new(CLAIM_SIGNATURE_INSIDE_VALIDITY).set_kind(LogKind::Success),
);

assert_eq!(
validation_results.validation_state(),
ValidationState::Valid
);
}

#[test]
fn valid_state_with_untrusted_delta() {
let mut validation_results = ValidationResults::default();

validation_results.add_status(
ValidationStatus::new(CLAIM_SIGNATURE_VALIDATED).set_kind(LogKind::Success),
);
validation_results.add_status(
ValidationStatus::new(CLAIM_SIGNATURE_INSIDE_VALIDITY).set_kind(LogKind::Success),
);

validation_results.add_status(
ValidationStatus::new_failure(SIGNING_CREDENTIAL_UNTRUSTED).set_ingredient_uri("1"),
);

assert_eq!(
validation_results.validation_state(),
ValidationState::Valid
);
}

#[test]
fn not_valid_state_with_failure() {
let mut validation_results = ValidationResults::default();

validation_results.add_status(
ValidationStatus::new(CLAIM_SIGNATURE_VALIDATED).set_kind(LogKind::Success),
);
validation_results.add_status(
ValidationStatus::new(CLAIM_SIGNATURE_INSIDE_VALIDITY).set_kind(LogKind::Success),
);

validation_results.add_status(ValidationStatus::new_failure(CLAIM_MALFORMED));

assert_eq!(
validation_results.validation_state(),
ValidationState::Invalid
);
}

#[test]
fn valid_state_with_failure_delta_and_untrusted_delta() {
let mut validation_results = ValidationResults::default();

validation_results.add_status(
ValidationStatus::new(CLAIM_SIGNATURE_VALIDATED).set_kind(LogKind::Success),
);
validation_results.add_status(
ValidationStatus::new(CLAIM_SIGNATURE_INSIDE_VALIDITY).set_kind(LogKind::Success),
);

validation_results.add_status(
ValidationStatus::new_failure(SIGNING_CREDENTIAL_UNTRUSTED).set_ingredient_uri("1"),
);
validation_results.add_status(
ValidationStatus::new_failure(ASSERTION_DATAHASH_MISMATCH).set_ingredient_uri("1"),
);

assert_eq!(
validation_results.validation_state(),
ValidationState::Invalid
);
}

#[test]
fn not_valid_state_with_failure_delta() {
let mut validation_results = ValidationResults::default();

validation_results.add_status(
ValidationStatus::new(CLAIM_SIGNATURE_VALIDATED).set_kind(LogKind::Success),
);
validation_results.add_status(
ValidationStatus::new(CLAIM_SIGNATURE_INSIDE_VALIDITY).set_kind(LogKind::Success),
);

validation_results.add_status(
ValidationStatus::new_failure(ASSERTION_DATAHASH_MISMATCH).set_ingredient_uri("1"),
);

assert_eq!(
validation_results.validation_state(),
ValidationState::Invalid
);
}

#[test]
fn not_valid_state_with_no_inside_validity() {
let mut validation_results = ValidationResults::default();

validation_results.add_status(
ValidationStatus::new(CLAIM_SIGNATURE_VALIDATED).set_kind(LogKind::Success),
);

assert_eq!(
validation_results.validation_state(),
ValidationState::Invalid
);
}

#[test]
fn not_valid_state_with_no_validated() {
let mut validation_results = ValidationResults::default();

validation_results.add_status(
ValidationStatus::new(CLAIM_SIGNATURE_INSIDE_VALIDITY).set_kind(LogKind::Success),
);

assert_eq!(
validation_results.validation_state(),
ValidationState::Invalid
);
}

#[test]
fn invalid_state() {
let mut validation_results = ValidationResults::default();

validation_results.add_status(ValidationStatus::new_failure(ASSERTION_DATAHASH_MISMATCH));

assert_eq!(
validation_results.validation_state(),
ValidationState::Invalid
);
}

#[test]
fn invalid_state_with_nothing() {
let validation_results = ValidationResults::default();
assert_eq!(
validation_results.validation_state(),
ValidationState::Invalid
);
}
}
Loading