Skip to content

Commit cfd374a

Browse files
authored
feat: Refine validation state logic (#1541)
* feat: add well-formed validation state * docs: clarify validation state and manifest stores * fix: rename invalid to malformed * fix: add more checks to validation state * fix: add unknown validation state * test: check for ==Trusted instead of !=Malformed * test: mark XCA.json as Malformed instead of Invalid * feat: make Valid state more strict and remove WellFormed state * test: reset settings before running test_builder_base_path * fix: if there is no active manifest, assume it wasn't validated * fix: remove unknown validation state for now * docs: clarify invalid state may occur if validation is disabled
1 parent 3ce3898 commit cfd374a

File tree

4 files changed

+79
-38
lines changed

4 files changed

+79
-38
lines changed

sdk/src/builder.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2531,7 +2531,7 @@ mod tests {
25312531
let manifest_store = Reader::from_file(&dest).expect("from_bytes");
25322532

25332533
println!("{manifest_store}");
2534-
assert_ne!(manifest_store.validation_state(), ValidationState::Invalid);
2534+
assert_eq!(manifest_store.validation_state(), ValidationState::Trusted);
25352535
assert_eq!(manifest_store.validation_status(), None);
25362536
assert_eq!(
25372537
manifest_store.active_manifest().unwrap().title().unwrap(),
@@ -2589,7 +2589,7 @@ mod tests {
25892589
//println!("{}", manifest_store);
25902590
if format != "c2pa" {
25912591
// c2pa files will not validate since they have no associated asset
2592-
assert_ne!(manifest_store.validation_state(), ValidationState::Invalid);
2592+
assert_eq!(manifest_store.validation_state(), ValidationState::Trusted);
25932593
}
25942594
assert_eq!(
25952595
manifest_store.active_manifest().unwrap().title().unwrap(),
@@ -2889,6 +2889,9 @@ mod tests {
28892889
#[cfg(feature = "file_io")]
28902890
#[test]
28912891
fn test_builder_base_path() {
2892+
#[cfg(target_os = "wasi")]
2893+
Settings::reset().unwrap();
2894+
28922895
let mut source = Cursor::new(TEST_IMAGE_CLEAN);
28932896
let mut dest = Cursor::new(Vec::new());
28942897

@@ -2917,7 +2920,7 @@ mod tests {
29172920
let reader = Reader::from_stream("image/jpeg", &mut dest).expect("from_bytes");
29182921

29192922
//println!("{}", reader);
2920-
assert_ne!(reader.validation_state(), ValidationState::Invalid);
2923+
assert_eq!(reader.validation_state(), ValidationState::Trusted);
29212924
assert_eq!(reader.validation_status(), None);
29222925
assert_eq!(
29232926
reader

sdk/src/validation_results.rs

Lines changed: 71 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
// specific language governing permissions and limitations under
1212
// each license.
1313

14+
use std::collections::HashSet;
15+
1416
#[cfg(feature = "json_schema")]
1517
use schemars::JsonSchema;
1618
use serde::{Deserialize, Serialize};
@@ -21,30 +23,48 @@ use crate::{
2123
jumbf::labels::manifest_label_from_uri,
2224
status_tracker::{LogKind, StatusTracker},
2325
store::Store,
24-
validation_status::{log_kind, ValidationStatus},
26+
validation_status::{self, log_kind, ValidationStatus},
2527
};
2628

29+
/// Represents the levels of assurance a manifest store achives when evaluated against the C2PA
30+
/// specifications structural, cryptographic, and trust requirements.
31+
///
32+
/// See [§14.3. Validation states].
33+
///
34+
/// [§14.3. Validation states]: https://spec.c2pa.org/specifications/specifications/2.2/specs/C2PA_Specification.html#_validation_states
2735
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
2836
#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
29-
/// Indicates if the manifest store is valid and trusted.
30-
///
31-
/// The Trusted state implies the manifest store is valid and the active signature is trusted.
3237
pub enum ValidationState {
33-
/// Errors were found in the manifest store.
38+
/// The manifest store fails to meet [ValidationState::WellFormed] requirements, meaning it cannot
39+
/// even be parsed or its basic structure is non-compliant.
40+
///
41+
/// This case may also occur if validation is disabled in the SDK.
3442
Invalid,
35-
/// No errors were found in validation, but the active signature is not trusted.
43+
/// The manifest store is well-formed and the cryptographic integrity checks succeed.
44+
///
45+
/// See [§14.3.5. Valid Manifest].
46+
///
47+
/// [§14.3.5. Valid Manifest]: https://spec.c2pa.org/specifications/specifications/2.2/specs/C2PA_Specification.html#_valid_manifest
3648
Valid,
37-
/// The manifest store is valid and the active signature is trusted.
49+
/// The manifest store is valid and signed by a certificate that chains up to a trusted root or known
50+
/// authority in the trust list.
51+
///
52+
/// See [§14.3.6. Trusted Manifest].
53+
///
54+
/// [§14.3.6. Trusted Manifest]: https://spec.c2pa.org/specifications/specifications/2.2/specs/C2PA_Specification.html#_trusted_manifest
3855
Trusted,
3956
}
4057

58+
/// Contains a set of success, informational, and failure validation status codes.
4159
#[derive(Clone, Serialize, Default, Deserialize, Debug, PartialEq, Eq)]
4260
#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
43-
/// Contains a set of success, informational, and failure validation status codes.
4461
pub struct StatusCodes {
45-
pub success: Vec<ValidationStatus>, // an array of validation success codes. May be empty.
46-
pub informational: Vec<ValidationStatus>, // an array of validation informational codes. May be empty.
47-
pub failure: Vec<ValidationStatus>, // an array of validation failure codes. May be empty.
62+
/// An array of validation success codes. May be empty.
63+
pub success: Vec<ValidationStatus>,
64+
/// An array of validation informational codes. May be empty.
65+
pub informational: Vec<ValidationStatus>,
66+
// An array of validation failure codes. May be empty.
67+
pub failure: Vec<ValidationStatus>,
4868
}
4969

5070
impl StatusCodes {
@@ -85,18 +105,22 @@ impl StatusCodes {
85105
}
86106
}
87107

88-
#[derive(Clone, Serialize, Default, Deserialize, Debug, PartialEq, Eq)]
89-
#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
90108
/// A map of validation results for a manifest store.
91109
///
92110
/// The map contains the validation results for the active manifest and any ingredient deltas.
93111
/// It is normal for there to be many
112+
#[derive(Clone, Serialize, Default, Deserialize, Debug, PartialEq, Eq)]
113+
#[cfg_attr(feature = "json_schema", derive(JsonSchema))]
94114
pub struct ValidationResults {
115+
/// Validation status codes for the ingredient's active manifest. Present if ingredient is a C2PA
116+
/// asset. Not present if the ingredient is not a C2PA asset.
95117
#[serde(rename = "activeManifest", skip_serializing_if = "Option::is_none")]
96-
active_manifest: Option<StatusCodes>, // Validation status codes for the ingredient's active manifest. Present if ingredient is a C2PA asset. Not present if the ingredient is not a C2PA asset.
118+
active_manifest: Option<StatusCodes>,
97119

120+
/// List of any changes/deltas between the current and previous validation results for each ingredient's
121+
/// manifest. Present if the the ingredient is a C2PA asset.
98122
#[serde(rename = "ingredientDeltas", skip_serializing_if = "Option::is_none")]
99-
ingredient_deltas: Option<Vec<IngredientDeltaValidationResult>>, // List of any changes/deltas between the current and previous validation results for each ingredient's manifest. Present if the the ingredient is a C2PA asset.
123+
ingredient_deltas: Option<Vec<IngredientDeltaValidationResult>>,
100124
}
101125

102126
impl ValidationResults {
@@ -180,29 +204,43 @@ impl ValidationResults {
180204
}
181205

182206
/// Returns the [ValidationState] of the manifest store based on the validation results.
207+
///
208+
/// See [§14.3. Validation states].
209+
///
210+
/// [§14.3. Validation states]: https://spec.c2pa.org/specifications/specifications/2.2/specs/C2PA_Specification.html#_validation_states
183211
pub fn validation_state(&self) -> ValidationState {
184-
let mut is_trusted = true; // Assume the state is trusted until proven otherwise
185212
if let Some(active_manifest) = self.active_manifest.as_ref() {
186-
if !active_manifest.failure().is_empty() {
187-
return ValidationState::Invalid;
188-
}
189-
// There must be a trusted credential in the active manifest for the state to be trusted
190-
is_trusted = active_manifest.success().iter().any(|status| {
191-
status.code() == crate::validation_status::SIGNING_CREDENTIAL_TRUSTED
213+
let success_codes: HashSet<&str> = active_manifest
214+
.success()
215+
.iter()
216+
.map(|status| status.code())
217+
.collect();
218+
let failure_codes = active_manifest.failure();
219+
let ingredient_failure = self.ingredient_deltas.as_ref().is_some_and(|deltas| {
220+
deltas
221+
.iter()
222+
.any(|idv| !idv.validation_deltas().failure().is_empty())
192223
});
193-
}
194-
if let Some(ingredient_deltas) = self.ingredient_deltas.as_ref() {
195-
for idv in ingredient_deltas.iter() {
196-
if !idv.validation_deltas().failure().is_empty() {
197-
return ValidationState::Invalid;
198-
}
224+
225+
// https://spec.c2pa.org/specifications/specifications/2.2/specs/C2PA_Specification.html#_valid_manifest
226+
let is_valid = success_codes.contains(validation_status::CLAIM_SIGNATURE_VALIDATED)
227+
&& success_codes.contains(validation_status::CLAIM_SIGNATURE_INSIDE_VALIDITY)
228+
&& failure_codes.is_empty()
229+
&& !ingredient_failure;
230+
231+
// https://spec.c2pa.org/specifications/specifications/2.2/specs/C2PA_Specification.html#_trusted_manifest
232+
let is_trusted = success_codes.contains(validation_status::SIGNING_CREDENTIAL_TRUSTED)
233+
&& failure_codes.is_empty()
234+
&& is_valid;
235+
236+
if is_trusted {
237+
return ValidationState::Trusted;
238+
} else if is_valid {
239+
return ValidationState::Valid;
199240
}
200241
}
201-
if is_trusted {
202-
ValidationState::Trusted
203-
} else {
204-
ValidationState::Valid
205-
}
242+
243+
ValidationState::Invalid
206244
}
207245

208246
/// Returns a list of all validation errors in [ValidationResults].

sdk/tests/known_good/XCA.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,4 +171,4 @@
171171
]
172172
},
173173
"validation_state": "Invalid"
174-
}
174+
}

sdk/tests/test_builder.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ fn test_builder_embedded_v1_otgp() -> Result<()> {
219219
dest.set_position(0);
220220
let reader = Reader::from_stream(format, &mut dest)?;
221221
// check that the v1 OTGP is embedded and we catch it correct with validation_results
222-
assert_ne!(reader.validation_state(), ValidationState::Invalid);
222+
assert_eq!(reader.validation_state(), ValidationState::Trusted);
223223
//println!("reader: {}", reader);
224224
assert_eq!(
225225
reader.active_manifest().unwrap().ingredients()[0]

0 commit comments

Comments
 (0)