Skip to content

Commit fca7422

Browse files
fix: Restore recursive ingredient checks (#1582)
* Make logging results more consistent. Catch bad manifest labels for V2 and > Claims Restore recursive check since validation results work has been pushed * formatting fix * review changes * Add a unit test
1 parent f6f8998 commit fca7422

File tree

6 files changed

+108
-93
lines changed

6 files changed

+108
-93
lines changed

docs/settings.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ Here's the JSON with all default values:
6464
"verify_timestamp_trust": true,
6565
"ocsp_fetch": false,
6666
"remote_manifest_fetch": true,
67-
"check_ingredient_trust": true,
6867
"skip_ingredient_conflict_resolution": false,
6968
"strict_v1_validation": false
7069
},
@@ -282,7 +281,6 @@ The `verify` object specifies verification behavior.
282281
| `verify.verify_timestamp_trust` | Boolean | Verify time-stamp certificates | true |
283282
| `verify.ocsp_fetch` | Boolean | Fetch OCSP status during validation | false |
284283
| `verify.remote_manifest_fetch` | Boolean | Fetch remote manifests | true |
285-
| `verify.check_ingredient_trust` | Boolean | Verify ingredient certificates | true |
286284
| `verify.skip_ingredient_conflict_resolution` | Boolean | Skip ingredient conflict resolution | false |
287285
| `verify.strict_v1_validation` | Boolean | Use strict C2PA v1 validation | false |
288286

sdk/src/claim.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ use crate::{
6565
labels::{
6666
assertion_label_from_uri, box_name_from_uri, manifest_label_from_uri,
6767
manifest_label_to_parts, to_absolute_uri, to_assertion_uri, to_databox_uri,
68-
to_signature_uri, ASSERTIONS, CLAIM, CREDENTIALS, DATABOX, DATABOXES, SIGNATURE,
68+
to_manifest_uri, to_signature_uri, ASSERTIONS, CLAIM, CREDENTIALS, DATABOX, DATABOXES,
69+
SIGNATURE,
6970
},
7071
},
7172
jumbf_io::get_assetio_handler,
@@ -1861,6 +1862,17 @@ impl Claim {
18611862
.failure(validation_log, Error::ClaimMissingSignatureBox)?;
18621863
}
18631864

1865+
// for V2 and greater claims the label must conform
1866+
if claim.version() > 1 && manifest_label_to_parts(claim.label()).is_none() {
1867+
log_item!(
1868+
to_manifest_uri(claim.label()),
1869+
"claim box label invalid",
1870+
"verify_claim_async"
1871+
)
1872+
.validation_status(validation_status::CLAIM_MALFORMED)
1873+
.failure(validation_log, Error::ClaimInvalidContent)?;
1874+
}
1875+
18641876
let sign1 = parse_cose_sign1(&sig, &data, validation_log)?;
18651877
let certificate_serial_num = get_signing_cert_serial_num(&sign1)?.to_string();
18661878

@@ -1940,6 +1952,17 @@ impl Claim {
19401952
.failure(validation_log, Error::ClaimMissingSignatureBox)?;
19411953
}
19421954

1955+
// for V2 and greater claims the label must conform
1956+
if claim.version() > 1 && manifest_label_to_parts(claim.label()).is_none() {
1957+
log_item!(
1958+
to_manifest_uri(claim.label()),
1959+
"claim box label invalid",
1960+
"verify_claim"
1961+
)
1962+
.validation_status(validation_status::CLAIM_MALFORMED)
1963+
.failure(validation_log, Error::ClaimInvalidContent)?;
1964+
}
1965+
19431966
// If we are validating a claim that has been loaded from a file
19441967
// we need the original data but if we are signing, we generate the data
19451968
// This avoids cloning the data when we are only referencing it.
@@ -4110,6 +4133,14 @@ pub mod tests {
41104133
1,
41114134
);
41124135
assert!(c4.is_err());
4136+
4137+
// bad v2
4138+
let c5 = Claim::new_with_user_guid(
4139+
"claim_generator",
4140+
"urn:blahblah:3fad1ead-8ed5-44d0-873b-ea5f58adea82:acme",
4141+
2,
4142+
);
4143+
assert!(c5.is_err());
41134144
}
41144145

41154146
#[test]

sdk/src/jumbf/labels.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,11 @@ pub(crate) fn manifest_label_to_parts(uri: &str) -> Option<ManifestParts> {
243243
if parts[0] == "urn" {
244244
is_v1 = parts[1] == "uuid";
245245

246+
// if > v1 it must be c2pa namespace
247+
if !is_v1 && parts[1] != "c2pa" {
248+
return None;
249+
}
250+
246251
guid = parts[2].to_owned();
247252

248253
if !is_v1 {

sdk/src/settings/mod.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -305,10 +305,7 @@ pub struct Verify {
305305
/// [`Ingredient`]: crate::Ingredient
306306
/// [`Builder`]: crate::Builder
307307
pub remote_manifest_fetch: bool,
308-
/// Whether to verify ingredient certificates against the trust lists specific in [`Trust`].
309308
///
310-
/// The default value is true.
311-
pub(crate) check_ingredient_trust: bool,
312309
/// Whether to skip ingredient conflict resolution when multiple ingredients have the same
313310
/// manifest identifier. This settings is only applicable for C2PA v2 validation.
314311
///
@@ -332,7 +329,6 @@ impl Default for Verify {
332329
verify_timestamp_trust: !cfg!(test), // verify timestamp trust unless in test mode
333330
ocsp_fetch: false,
334331
remote_manifest_fetch: true,
335-
check_ingredient_trust: true,
336332
skip_ingredient_conflict_resolution: false,
337333
strict_v1_validation: false,
338334
}
@@ -855,7 +851,6 @@ pub mod tests {
855851
verify_trust = true
856852
ocsp_fetch = false
857853
remote_manifest_fetch = true
858-
check_ingredient_trust = true
859854
skip_ingredient_conflict_resolution = false
860855
strict_v1_validation = false
861856
}

sdk/src/store.rs

Lines changed: 25 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1669,14 +1669,11 @@ impl Store {
16691669
"ingredient hash does not match found ingredient".to_string(),
16701670
),
16711671
)?;
1672-
return Err(Error::HashMismatch(
1673-
"ingredient hash does not match found ingredient".to_string(),
1674-
)); // hard stop regardless of StatusTracker mode
16751672
}
16761673

1677-
// if manifest hash did not match and this is a V2 or greater claim then we
1674+
// if manifest hash did not match because of redaction and this is a V2 or greater claim then we
16781675
// must try the signature validation method before proceeding
1679-
if !manifests_match && ingredient_version > 1 {
1676+
if !manifests_match && has_redactions && ingredient_version > 1 {
16801677
let claim_signature =
16811678
ingredient_assertion.signature().ok_or_else(|| {
16821679
log_item!(
@@ -1724,10 +1721,6 @@ impl Store {
17241721
"ingredient claimSignature mismatch".to_string(),
17251722
),
17261723
)?;
1727-
return Err(Error::HashMismatch(
1728-
"ingredient signature box hash does not match found ingredient"
1729-
.to_string(),
1730-
)); // hard stop regardless of StatusTracker mode
17311724
}
17321725
}
17331726

@@ -1737,19 +1730,16 @@ impl Store {
17371730
Claim::verify_hash_binding(ingredient, asset_data, svi, validation_log)?;
17381731
}
17391732

1740-
// if manifest hash did not match we continue on to do a full claim validation
1741-
if !manifests_match {
1742-
Claim::verify_claim(
1743-
ingredient,
1744-
asset_data,
1745-
svi,
1746-
check_ingredient_trust,
1747-
&store.ctp,
1748-
validation_log,
1749-
http_resolver,
1750-
settings,
1751-
)?;
1752-
}
1733+
Claim::verify_claim(
1734+
ingredient,
1735+
asset_data,
1736+
svi,
1737+
check_ingredient_trust,
1738+
&store.ctp,
1739+
validation_log,
1740+
http_resolver,
1741+
settings,
1742+
)?;
17531743

17541744
// recurse nested ingredients
17551745
Store::ingredient_checks(
@@ -1856,7 +1846,7 @@ impl Store {
18561846

18571847
// allow the extra ingredient trust checks
18581848
// these checks are to prevent the trust spoofing
1859-
let check_ingredient_trust = settings.verify.check_ingredient_trust;
1849+
let check_ingredient_trust = settings.verify.verify_trust;
18601850

18611851
// get the 1.1-1.2 box hash
18621852
let ingredient_hashes = store.get_manifest_box_hashes(ingredient);
@@ -1903,14 +1893,11 @@ impl Store {
19031893
"ingredient hash does not match found ingredient".to_string(),
19041894
),
19051895
)?;
1906-
return Err(Error::HashMismatch(
1907-
"ingredient hash does not match found ingredient".to_string(),
1908-
)); // hard stop regardless of StatusTracker mode
19091896
}
19101897

19111898
// if manifest hash did not match and this is a V2 or greater claim then we
19121899
// must try the signature validation method before proceeding
1913-
if !manifests_match && ingredient_version > 1 {
1900+
if !manifests_match && has_redactions && ingredient_version > 1 {
19141901
let claim_signature =
19151902
ingredient_assertion.signature().ok_or_else(|| {
19161903
log_item!(
@@ -1958,10 +1945,6 @@ impl Store {
19581945
"ingredient claimSignature mismatch".to_string(),
19591946
),
19601947
)?;
1961-
return Err(Error::HashMismatch(
1962-
"ingredient signature box hash does not match found ingredient"
1963-
.to_string(),
1964-
)); // hard stop regardless of StatusTracker mode
19651948
}
19661949
}
19671950

@@ -1971,20 +1954,17 @@ impl Store {
19711954
Claim::verify_hash_binding(ingredient, asset_data, svi, validation_log)?;
19721955
}
19731956

1974-
// if manifest hash did not match we continue on to do a full claim validation
1975-
if !manifests_match {
1976-
Claim::verify_claim_async(
1977-
ingredient,
1978-
asset_data,
1979-
svi,
1980-
check_ingredient_trust,
1981-
&store.ctp,
1982-
validation_log,
1983-
http_resolver,
1984-
settings,
1985-
)
1986-
.await?;
1987-
}
1957+
Claim::verify_claim_async(
1958+
ingredient,
1959+
asset_data,
1960+
svi,
1961+
check_ingredient_trust,
1962+
&store.ctp,
1963+
validation_log,
1964+
http_resolver,
1965+
settings,
1966+
)
1967+
.await?;
19881968

19891969
// recurse nested ingredients
19901970
Box::pin(Store::ingredient_checks_async(

sdk/tests/integration.rs

Lines changed: 46 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -349,44 +349,50 @@ mod integration_1 {
349349
Ok(())
350350
}
351351

352-
#[test]
353-
#[cfg(feature = "file_io")]
354-
fn test_certificate_status() -> Result<()> {
355-
use c2pa::ValidationState;
356-
357-
Settings::from_toml(include_str!("../tests/fixtures/test_settings.toml"))?;
358-
Settings::from_toml(
359-
&toml::toml! {
360-
[builder]
361-
certificate_status_fetch = "all"
362-
certificate_status_should_override = true
363-
}
364-
.to_string(),
365-
)?;
366-
367-
// set up parent and destination paths
368-
let temp_dir = tempdirectory()?;
369-
let output_path = temp_dir.path().join("test_file.jpg");
370-
let parent_path = fixture_path("ocsp.jpg");
371-
372-
// create a new Manifest
373-
let mut builder = Builder::new();
374-
builder.set_intent(c2pa::BuilderIntent::Update);
375-
376-
// sign and embed into the target file
377-
let signer = Settings::signer()?;
378-
builder.sign_file(signer.as_ref(), &parent_path, &output_path)?;
379-
380-
// std::fs::copy(&output_path, "cert_status.jpg")?;
381-
382-
// read our new file with embedded manifest
383-
let reader = Reader::from_file(&output_path)?;
384-
let reader_json = reader.json();
385-
//println!("{reader}");
386-
// ensure certificate status assertion was created
387-
//assert!(reader_json.contains(r#"label": "c2pa.certificate-status"#));
388-
assert_eq!(reader.validation_state(), ValidationState::Trusted);
389-
assert!(reader_json.contains("signingCredential.ocsp.notRevoked"));
390-
Ok(())
391-
}
352+
/*
353+
This test is currently invalid. It is using C2PA 2.2 assertions in 1.4 claims
354+
This needs to be rewritten in a way that does not require network calls, or mock
355+
them correctly. Tracking issue: https://github.com/contentauth/c2pa-rs/issues/1581
356+
357+
#[test]
358+
#[cfg(feature = "file_io")]
359+
fn test_certificate_status() -> Result<()> {
360+
use c2pa::ValidationState;
361+
362+
Settings::from_toml(include_str!("../tests/fixtures/test_settings.toml"))?;
363+
Settings::from_toml(
364+
&toml::toml! {
365+
[builder]
366+
certificate_status_fetch = "all"
367+
certificate_status_should_override = true
368+
}
369+
.to_string(),
370+
)?;
371+
372+
// set up parent and destination paths
373+
let temp_dir = tempdirectory()?;
374+
let output_path = temp_dir.path().join("test_file.jpg");
375+
let parent_path = fixture_path("ocsp.jpg");
376+
377+
// create a new Manifest
378+
let mut builder = Builder::new();
379+
builder.set_intent(c2pa::BuilderIntent::Update);
380+
381+
// sign and embed into the target file
382+
let signer = Settings::signer()?;
383+
builder.sign_file(signer.as_ref(), &parent_path, &output_path)?;
384+
385+
// std::fs::copy(&output_path, "cert_status.jpg")?;
386+
387+
// read our new file with embedded manifest
388+
let reader = Reader::from_file(&output_path)?;
389+
let reader_json = reader.json();
390+
//println!("{reader}");
391+
// ensure certificate status assertion was created
392+
//assert!(reader_json.contains(r#"label": "c2pa.certificate-status"#));
393+
assert_eq!(reader.validation_state(), ValidationState::Trusted);
394+
assert!(reader_json.contains("signingCredential.ocsp.notRevoked"));
395+
Ok(())
396+
}
397+
*/
392398
}

0 commit comments

Comments
 (0)