| // Copyright 2026 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| |
| #include "assertion/validator.h" |
| |
| #include <string> |
| #include <vector> |
| |
| #include "absl/strings/escaping.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/strings/substitute.h" |
| #include "absl/types/span.h" |
| #include "constants/status_codes.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include "jumbf/box.h" |
| #include "jumbf/uri.h" |
| #include "proto/assertion.pb.h" |
| #include "proto/hashed_uri.pb.h" |
| #include "proto/manifest.pb.h" |
| #include "testing/cbor_utils.h" |
| #include "testing/jumbf_utils.h" |
| #include "testing/proto_test_utils.h" |
| #include "testing/test_validation_tracker.h" |
| |
| namespace credentio { |
| namespace { |
| |
| using ::cbor::FromJson; |
| using ::credentio_testing::EqualsProto; |
| using ::credentio_testing::ParseTextProtoOrDie; |
| using ::credentio_testing::PartiallyEqualsProto; |
| using ::testing::Contains; |
| using ::testing::ElementsAre; |
| using ::testing::IsEmpty; |
| using ::testing::SizeIs; |
| |
| constexpr absl::string_view kManifestLabel1 = |
| "urn:uuid:F9168C5E-CEB2-4FAA-B6BF-329BF39FA1E4"; |
| constexpr absl::string_view kManifestLabel2 = |
| "urn:uuid:AFAFAFAF-0101-0202-0303-ABCDEFFEDCBA"; |
| constexpr absl::string_view kManifestLabel3 = |
| "urn:uuid:ABABABAB-0404-0505-0606-FFFFEEEEDDDD"; |
| |
| void AddCreatedAssertion(Claim* claim, jumbf::SuperBox assertion) { |
| absl::string_view algorithm = "sha256"; |
| auto* created_assertion = claim->add_created_assertions(); |
| created_assertion->set_url( |
| absl::StrCat("self#jumbf=c2pa.assertions/", |
| assertion.description.label.value_or("UNKNOWN_LABEL"))); |
| created_assertion->set_algorithm(algorithm); |
| created_assertion->set_hash(GetHash(algorithm, assertion)); |
| } |
| |
| class AssertionValidatorTest : public ::testing::Test { |
| protected: |
| std::string ingredient_assertion_cbor_ = cbor::FromJson(R"json( |
| { |
| "dc:title": "ingredient-title", |
| "dc:format": "ingredient-format", |
| "relationship": "componentOf", |
| "instanceID": "ingredient-instance-id", |
| "data": { |
| "url": "ingredient-data-url", |
| "alg": "ingredient-data-alg", |
| "hash": "b64'aGFzaA=='", |
| "size": 1234567890 |
| }, |
| "description": "ingredient-description", |
| "informationalURI": "ingredient-informational-uri", |
| "metadata": { |
| "reviewRatings": [ |
| { |
| "value": 1, |
| "explanation": "can't dance to it" |
| } |
| ] |
| } |
| } |
| )json"); |
| std::string asset_reference_assertion_cbor_ = cbor::FromJson(R"json( |
| { |
| "references": [ |
| { |
| "description": "A copy of the asset on the web", |
| "reference": { |
| "uri": "https://some.storage.us/foo" |
| } |
| } |
| ] |
| } |
| )json"); |
| std::string data_hash_assertion_cbor_ = cbor::FromJson(R"json( |
| { |
| "alg": "sha256", |
| "pad": "b64'Zm9v'", |
| "hash": "b64'Zm9v'", |
| "name": "JUMBF manifest", |
| "exclusions": [ |
| { |
| "start": 9960, |
| "length": 4213 |
| }, |
| { |
| "start": 14173, |
| "length": 1000 |
| } |
| ] |
| })json"); |
| std::string embedded_file_ = "fake embedded file contents"; |
| std::string metadata_json_ = R"json( |
| { |
| "@context" : { |
| "exif": "http://ns.adobe.com/exif/1.0/", |
| "exifEX": "http://cipa.jp/exif/2.32/", |
| "tiff": "http://ns.adobe.com/tiff/1.0/", |
| "Iptc4xmpCore": "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/", |
| "Iptc4xmpExt": "http://iptc.org/std/Iptc4xmpExt/2008-02-29/", |
| "dc" : "http://purl.org/dc/elements/1.1/", |
| "photoshop" : "http://ns.adobe.com/photoshop/1.0/" |
| }, |
| "photoshop:DateCreated": "Aug 31, 2022", |
| "Iptc4xmpExt:DigitalSourceType": "https://cv.iptc.org/newscodes/digitalsourcetype/digitalCapture", |
| "Iptc4xmpExt:LocationCreated": { |
| "Iptc4xmpExt:City": "San Francisco" |
| }, |
| "Iptc4xmpExt:PersonInImage": [ |
| "Erika Fictional" |
| ], |
| "Iptc4xmpCore:AltTextAccessibility": "Photo of Erika Fictional standing in front of the Golden Gate Bridge at sunset.", |
| "exif:GPSVersionID": "2.2.0.0", |
| "exif:GPSLatitude": "39,21.102N", |
| "exif:GPSLongitude": "74,26.5737W", |
| "exif:GPSAltitudeRef": 0, |
| "exif:GPSAltitude": "100963/29890", |
| "exif:GPSTimeStamp": "2019-09-22T18:22:57Z", |
| "exif:GPSSpeedRef": "K", |
| "exif:GPSSpeed": "4009/161323", |
| "exif:GPSImgDirectionRef": "T", |
| "exif:GPSImgDirection": "296140/911", |
| "exif:GPSDestBearingRef": "T", |
| "exif:GPSDestBearing": "296140/911", |
| "exif:GPSHPositioningError": "13244/2207", |
| "exif:ExposureTime": "1/100", |
| "exif:FNumber": 4.0, |
| "exif:ColorSpace": 1, |
| "exif:DigitalZoomRatio": 2.0, |
| "tiff:Make": "CameraCompany", |
| "tiff:Model": "Shooter S1", |
| "exifEX:LensMake": "CameraCompany", |
| "exifEX:LensModel": "17.0-35.0 mm", |
| "exifEX:LensSpecification": { "@list": [ 1.55, 4.2, 1.6, 2.4 ] } |
| } |
| )json"; |
| }; |
| |
| TEST_F(AssertionValidatorTest, ValidateOk) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3__1" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.metadata" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| redacted_assertions: "self#jumbf=c2pa.some_redacted_assertion" |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.ingredient.v3", |
| ingredient_assertion_cbor_), |
| CreateAssertionBoxFromCbor("c2pa.ingredient.v3__1", |
| ingredient_assertion_cbor_), |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| CreateAssertionBoxFromJson("c2pa.metadata", metadata_json_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailureStatuses(), IsEmpty()); |
| EXPECT_THAT( |
| manifest.assertions(), |
| ElementsAre( |
| EqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.hash.data" |
| data_hash { |
| exclusions { start: 9960 length: 4213 } |
| exclusions { start: 14173 length: 1000 } |
| alg: "sha256" |
| hash: "foo" |
| name: "JUMBF manifest" |
| } |
| )pb")), |
| EqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.ingredient.v3" |
| ingredient_v3 { |
| title: "ingredient-title" |
| media_type: "ingredient-format" |
| relationship: "componentOf" |
| instance_id: "ingredient-instance-id" |
| data { |
| url: "ingredient-data-url" |
| algorithm: "ingredient-data-alg" |
| hash: "hash" |
| size: 1234567890 |
| } |
| description: "ingredient-description" |
| informational_uri: "ingredient-informational-uri" |
| } |
| )pb")), |
| EqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.ingredient.v3__1" |
| ingredient_v3 { |
| title: "ingredient-title" |
| media_type: "ingredient-format" |
| relationship: "componentOf" |
| instance_id: "ingredient-instance-id" |
| data { |
| url: "ingredient-data-url" |
| algorithm: "ingredient-data-alg" |
| hash: "hash" |
| size: 1234567890 |
| } |
| description: "ingredient-description" |
| informational_uri: "ingredient-informational-uri" |
| } |
| )pb")), |
| EqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.metadata" |
| metadata { |
| value { |
| fields { |
| key: "@context" |
| value { |
| struct_value { |
| fields { |
| key: "Iptc4xmpCore" |
| value { |
| string_value: "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/" |
| } |
| } |
| fields { |
| key: "Iptc4xmpExt" |
| value { |
| string_value: "http://iptc.org/std/Iptc4xmpExt/2008-02-29/" |
| } |
| } |
| fields { |
| key: "dc" |
| value { |
| string_value: "http://purl.org/dc/elements/1.1/" |
| } |
| } |
| fields { |
| key: "exif" |
| value { string_value: "http://ns.adobe.com/exif/1.0/" } |
| } |
| fields { |
| key: "exifEX" |
| value { string_value: "http://cipa.jp/exif/2.32/" } |
| } |
| fields { |
| key: "photoshop" |
| value { |
| string_value: "http://ns.adobe.com/photoshop/1.0/" |
| } |
| } |
| fields { |
| key: "tiff" |
| value { string_value: "http://ns.adobe.com/tiff/1.0/" } |
| } |
| } |
| } |
| } |
| fields { |
| key: "Iptc4xmpCore:AltTextAccessibility" |
| value { |
| string_value: "Photo of Erika Fictional standing in front of the Golden Gate Bridge at sunset." |
| } |
| } |
| fields { |
| key: "Iptc4xmpExt:DigitalSourceType" |
| value { |
| string_value: "https://cv.iptc.org/newscodes/digitalsourcetype/digitalCapture" |
| } |
| } |
| fields { |
| key: "Iptc4xmpExt:LocationCreated" |
| value { |
| struct_value { |
| fields { |
| key: "Iptc4xmpExt:City" |
| value { string_value: "San Francisco" } |
| } |
| } |
| } |
| } |
| fields { |
| key: "Iptc4xmpExt:PersonInImage" |
| value { |
| list_value { values { string_value: "Erika Fictional" } } |
| } |
| } |
| fields { |
| key: "exif:ColorSpace" |
| value { number_value: 1 } |
| } |
| fields { |
| key: "exif:DigitalZoomRatio" |
| value { number_value: 2 } |
| } |
| fields { |
| key: "exif:ExposureTime" |
| value { string_value: "1/100" } |
| } |
| fields { |
| key: "exif:FNumber" |
| value { number_value: 4 } |
| } |
| fields { |
| key: "exif:GPSAltitude" |
| value { string_value: "100963/29890" } |
| } |
| fields { |
| key: "exif:GPSAltitudeRef" |
| value { number_value: 0 } |
| } |
| fields { |
| key: "exif:GPSDestBearing" |
| value { string_value: "296140/911" } |
| } |
| fields { |
| key: "exif:GPSDestBearingRef" |
| value { string_value: "T" } |
| } |
| fields { |
| key: "exif:GPSHPositioningError" |
| value { string_value: "13244/2207" } |
| } |
| fields { |
| key: "exif:GPSImgDirection" |
| value { string_value: "296140/911" } |
| } |
| fields { |
| key: "exif:GPSImgDirectionRef" |
| value { string_value: "T" } |
| } |
| fields { |
| key: "exif:GPSLatitude" |
| value { string_value: "39,21.102N" } |
| } |
| fields { |
| key: "exif:GPSLongitude" |
| value { string_value: "74,26.5737W" } |
| } |
| fields { |
| key: "exif:GPSSpeed" |
| value { string_value: "4009/161323" } |
| } |
| fields { |
| key: "exif:GPSSpeedRef" |
| value { string_value: "K" } |
| } |
| fields { |
| key: "exif:GPSTimeStamp" |
| value { string_value: "2019-09-22T18:22:57Z" } |
| } |
| fields { |
| key: "exif:GPSVersionID" |
| value { string_value: "2.2.0.0" } |
| } |
| fields { |
| key: "exifEX:LensMake" |
| value { string_value: "CameraCompany" } |
| } |
| fields { |
| key: "exifEX:LensModel" |
| value { string_value: "17.0-35.0 mm" } |
| } |
| fields { |
| key: "exifEX:LensSpecification" |
| value { |
| struct_value { |
| fields { |
| key: "@list" |
| value { |
| list_value { |
| values { number_value: 1.55 } |
| values { number_value: 4.2 } |
| values { number_value: 1.6 } |
| values { number_value: 2.4 } |
| } |
| } |
| } |
| } |
| } |
| } |
| fields { |
| key: "photoshop:DateCreated" |
| value { string_value: "Aug 31, 2022" } |
| } |
| fields { |
| key: "tiff:Make" |
| value { string_value: "CameraCompany" } |
| } |
| fields { |
| key: "tiff:Model" |
| value { string_value: "Shooter S1" } |
| } |
| } |
| } |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateActionsRedacted) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| redacted_assertions: "self#jumbf=c2pa.assertions/c2pa.actions" |
| )pb"); |
| jumbf::SuperBox manifest_store = |
| CreateManifestStore({CreateStandardManifest(kManifestLabel1, {})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kAssertionActionRedacted)); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateActionsV2Redacted) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| redacted_assertions: "self#jumbf=c2pa.assertions/c2pa.actions.v2" |
| )pb"); |
| jumbf::SuperBox manifest_store = |
| CreateManifestStore({CreateStandardManifest(kManifestLabel1, {})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kAssertionActionRedacted)); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateActionsMultiInstanceRedacted) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| redacted_assertions: "self#jumbf=c2pa.assertions/c2pa.actions.v2__1" |
| )pb"); |
| jumbf::SuperBox manifest_store = |
| CreateManifestStore({CreateStandardManifest(kManifestLabel1, {})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kAssertionActionRedacted)); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateUnresolvableUri) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "c2pa.assertions/unresolvable.assertion" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| redacted_assertions: "self#jumbf=c2pa.some_redacted_assertion" |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kAssertionMissing)); |
| EXPECT_THAT( |
| manifest.assertions(), |
| ElementsAre(PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.hash.data" |
| data_hash {} |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateAssertionMissing) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/no.such.assertion" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| redacted_assertions: "self#jumbf=c2pa.some_redacted_assertion" |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kAssertionMissing)); |
| EXPECT_THAT( |
| manifest.assertions(), |
| ElementsAre(PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.hash.data" |
| data_hash {} |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateAssertionMissingNotInAssertionStore) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.somewhere/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| redacted_assertions: "self#jumbf=c2pa.some_redacted_assertion" |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kAssertionMissing)); |
| EXPECT_THAT( |
| manifest.assertions(), |
| ElementsAre(PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.hash.data" |
| data_hash {} |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, |
| ValidateAssertionMissingNotChildOfAssertionStore) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/somewhere/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| redacted_assertions: "self#jumbf=c2pa.some_redacted_assertion" |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kAssertionMissing)); |
| EXPECT_THAT( |
| manifest.assertions(), |
| ElementsAre(PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.hash.data" |
| data_hash {} |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateAssertionMissingInNestedBox) { |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| jumbf::SuperBox{ |
| .description = {.label = "nested"}, |
| .contents = {{.payload = CreateAssertionBoxFromCbor( |
| "c2pa.hash.data", data_hash_assertion_cbor_)}}, |
| }, |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| auto manifest = ParseTextProtoOrDie<Manifest>(R"pb( |
| label: "urn:uuid:F9168C5E-CEB2-4FAA-B6BF-329BF39FA1E4" |
| claim { |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/nested/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| } |
| )pb"); |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kAssertionMissing)); |
| EXPECT_THAT(manifest.assertions(), IsEmpty()); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateThumbnailAssertion) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.thumbnail.claim.png" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.thumbnail.ingredient.png" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| CreateAssertionBoxFromEmbeddedFile("c2pa.thumbnail.claim.png", |
| "image/png", embedded_file_, |
| "claim.thumbnail.png"), |
| CreateAssertionBoxFromEmbeddedFile("c2pa.thumbnail.ingredient.png", |
| "image/png", embedded_file_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), IsEmpty()); |
| EXPECT_THAT( |
| manifest.assertions(), |
| ElementsAre(PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.hash.data" |
| data_hash {} |
| )pb")), |
| EqualsProto(ParseTextProtoOrDie<Assertion>( |
| R"pb(label: "c2pa.thumbnail.claim.png" |
| claim_thumbnail { |
| media_type: "image/png" |
| data: "fake embedded file contents" |
| file_name: "claim.thumbnail.png" |
| })pb")), |
| EqualsProto(ParseTextProtoOrDie<Assertion>( |
| R"pb(label: "c2pa.thumbnail.ingredient.png" |
| ingredient_thumbnail { |
| media_type: "image/png" |
| data: "fake embedded file contents" |
| })pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateUndeclaredAssertion) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| CreateAssertionBoxFromCbor("c2pa.ingredient.v3", |
| ingredient_assertion_cbor_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kAssertionUndeclared)); |
| EXPECT_THAT( |
| manifest.assertions(), |
| ElementsAre(PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.hash.data" |
| data_hash {} |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateMalformedAssertion) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| // Add a CBOR box to the assertion store. |
| assertion_store.contents.push_back({.payload = jumbf::CborBox{}}); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kAssertionUndeclared)); |
| EXPECT_THAT( |
| manifest.assertions(), |
| ElementsAre(PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.hash.data" |
| data_hash {} |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateSoftBindingMissingForWatermarkedBound) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.actions.v2" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| std::string actions_cbor = cbor::FromJson(R"json( |
| { |
| "actions": [ |
| { |
| "action": "c2pa.created" |
| }, |
| { |
| "action": "c2pa.watermarked.bound" |
| } |
| ] |
| } |
| )json"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| CreateAssertionBoxFromCbor("c2pa.actions.v2", actions_cbor), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT( |
| t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kAssertionActionSoftBindingMissing)); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateSoftBindingPresentForWatermarkedBound) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.actions.v2" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.soft-binding" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| std::string actions_cbor = cbor::FromJson(R"json( |
| { |
| "actions": [ |
| { |
| "action": "c2pa.created" |
| }, |
| { |
| "action": "c2pa.watermarked.bound" |
| } |
| ] |
| } |
| )json"); |
| |
| std::string soft_binding_cbor = cbor::FromJson(R"json( |
| { |
| "alg": "soft-algorithm", |
| "blocks": [ |
| { |
| "scope": { |
| "timespan": { |
| "start": 100, |
| "end": 200 |
| } |
| }, |
| "value": "b64'dmFsdWU='" |
| } |
| ] |
| } |
| )json"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| CreateAssertionBoxFromCbor("c2pa.actions.v2", actions_cbor), |
| CreateAssertionBoxFromCbor("c2pa.soft-binding", soft_binding_cbor), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailureStatuses(), IsEmpty()); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateSoftBindingMissingForWatermarked) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.actions.v2" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| std::string actions_cbor = cbor::FromJson(R"json( |
| { |
| "actions": [ |
| { |
| "action": "c2pa.created" |
| }, |
| { |
| "action": "c2pa.watermarked" |
| } |
| ] |
| } |
| )json"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| CreateAssertionBoxFromCbor("c2pa.actions.v2", actions_cbor), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT( |
| t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kAssertionActionSoftBindingMissing)); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateSoftBindingPresentForWatermarked) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.actions.v2" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.soft-binding" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| std::string actions_cbor = cbor::FromJson(R"json( |
| { |
| "actions": [ |
| { |
| "action": "c2pa.created" |
| }, |
| { |
| "action": "c2pa.watermarked" |
| } |
| ] |
| } |
| )json"); |
| std::string soft_binding_cbor = cbor::FromJson(R"json( |
| { |
| "alg": "soft-algorithm", |
| "blocks": [ |
| { |
| "scope": { |
| "timespan": { |
| "start": 100, |
| "end": 200 |
| } |
| }, |
| "value": "b64'dmFsdWU='" |
| } |
| ] |
| } |
| )json"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| CreateAssertionBoxFromCbor("c2pa.actions.v2", actions_cbor), |
| CreateAssertionBoxFromCbor("c2pa.soft-binding", soft_binding_cbor), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailureStatuses(), IsEmpty()); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateInaccessibleAssertion) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "https://foo.bar/my_assertion" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kAssertionMissing)); |
| EXPECT_THAT( |
| manifest.assertions(), |
| ElementsAre(PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.hash.data" |
| data_hash {} |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateInvalidCbor) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", "not valid cbor"), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kAssertionCborInvalid)); |
| EXPECT_THAT(manifest.assertions(), IsEmpty()); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateAssertionAbsolutePath) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=/c2pa/urn:uuid:F9168C5E-CEB2-4FAA-B6BF-329BF39FA1E4/c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), IsEmpty()); |
| EXPECT_THAT( |
| manifest.assertions(), |
| ElementsAre(PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.hash.data" |
| data_hash {} |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateAssertionOutsideManifest) { |
| // This URI points to the manifest with kManifestLabel2. |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=/c2pa/urn:uuid:AFAFAFAF-0101-0202-0303-ABCDEFFEDCBA/c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {}), |
| CreateStandardManifest(kManifestLabel2, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kAssertionOutsideManifest)); |
| EXPECT_THAT(manifest.assertions(), IsEmpty()); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateInvalidAlgorithm) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "not_an_algo" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kAlgorithmUnsupported)); |
| EXPECT_THAT(manifest.assertions(), IsEmpty()); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateInvalidHash) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "not_a_hash" |
| } |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kAssertionHashedUriMismatch)); |
| EXPECT_THAT(manifest.assertions(), IsEmpty()); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateInvalidHashSha384) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha384" |
| hash: "not_a_hash" |
| } |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kAssertionHashedUriMismatch)); |
| EXPECT_THAT(manifest.assertions(), IsEmpty()); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateInvalidHashSha512) { |
| Claim claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha512" |
| hash: "not_a_hash" |
| } |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kAssertionHashedUriMismatch)); |
| EXPECT_THAT(manifest.assertions(), IsEmpty()); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateActionsUnknown) { |
| std::string actions_cbor = cbor::FromJson(R"json( |
| { |
| "actions": [{"action": "c2pa.unknown"}] |
| } |
| )json"); |
| |
| Claim claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.actions" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.actions", actions_cbor), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT( |
| t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kGoogleAssertionActionUnknownAction)); |
| EXPECT_THAT(manifest.assertions(), IsEmpty()); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateActionsV2Unknown) { |
| std::string actions_cbor = cbor::FromJson(R"json( |
| { |
| "actions": [{"action": "c2pa.unknown"}] |
| } |
| )json"); |
| |
| Claim claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.actions.v2" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.actions.v2", actions_cbor), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT( |
| t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kGoogleAssertionActionUnknownAction)); |
| EXPECT_THAT(manifest.assertions(), IsEmpty()); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateActionsV2Created) { |
| std::string actions_cbor = cbor::FromJson(R"json( |
| { |
| "actions": [{"action": "c2pa.created"}] |
| } |
| )json"); |
| |
| Claim claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.actions.v2" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.actions.v2", actions_cbor), |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), IsEmpty()); |
| EXPECT_THAT( |
| manifest.assertions(), |
| ElementsAre(PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.actions.v2" |
| actions { actions { action: "c2pa.created" } } |
| )pb")), |
| PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>( |
| R"pb(label: "c2pa.hash.data")pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, InvalidStandardManifestNoHardBinding) { |
| std::string actions_cbor = cbor::FromJson(R"json( |
| { |
| "actions": [{"action": "c2pa.created"}] |
| } |
| )json"); |
| Claim claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.actions.v2" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.actions.v2", actions_cbor), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kClaimHardBindingsMissing)); |
| EXPECT_THAT( |
| manifest.assertions(), |
| ElementsAre(PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.actions.v2" |
| actions { actions { action: "c2pa.created" } } |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, InvalidStandardManifestMultipleHardBindings) { |
| std::string actions_cbor = cbor::FromJson(R"json( |
| { |
| "actions": [{"action": "c2pa.created"}] |
| } |
| )json"); |
| Claim claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.actions.v2" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.actions.v2", actions_cbor), |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT( |
| t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kAssertionMultipleHardBindings)); |
| EXPECT_THAT( |
| manifest.assertions(), |
| ElementsAre(PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.actions.v2" |
| actions { actions { action: "c2pa.created" } } |
| )pb")), |
| PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>( |
| R"pb(label: "c2pa.hash.data")pb")), |
| PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>( |
| R"pb(label: "c2pa.hash.data")pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, |
| InvalidStandardManifestMultipleParentIngredients) { |
| std::string ingredient_cbor = cbor::FromJson(R"json( |
| { |
| "relationship": "parentOf", |
| "instanceID": "ingredient-instance-id" |
| } |
| )json"); |
| std::string actions_cbor = cbor::FromJson(R"json( |
| { |
| "actions": [{"action": "c2pa.created"}] |
| } |
| )json"); |
| Claim claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.actions.v2" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3__1" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.actions.v2", actions_cbor), |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| CreateAssertionBoxFromCbor("c2pa.ingredient.v3", ingredient_cbor), |
| CreateAssertionBoxFromCbor("c2pa.ingredient.v3__1", ingredient_cbor), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kManifestMultipleParents)); |
| EXPECT_THAT( |
| manifest.assertions(), |
| ElementsAre(PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.actions.v2" |
| actions { actions { action: "c2pa.created" } } |
| )pb")), |
| PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>( |
| R"pb(label: "c2pa.hash.data")pb")), |
| PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>( |
| R"pb(label: "c2pa.ingredient.v3")pb")), |
| PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>( |
| R"pb(label: "c2pa.ingredient.v3__1")pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidUpdateManifest) { |
| jumbf::SuperBox std_assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| auto std_manifest = |
| CreateStandardManifest(kManifestLabel1, {std_assertion_store}); |
| |
| std::string ingredient_assertion_cbor = cbor::FromJson(absl::Substitute( |
| R"json( |
| { |
| "relationship": "parentOf", |
| "instanceID": "ingredient-instance-id", |
| "c2pa_manifest": { |
| "url": "self#jumbf=/c2pa/$0", |
| "alg": "sha256", |
| "hash": "b64'$1'" |
| } |
| } |
| )json", |
| kManifestLabel1, absl::Base64Escape(GetHash("sha256", std_manifest)))); |
| jumbf::SuperBox ingredient_assertion_box = CreateAssertionBoxFromCbor( |
| "c2pa.ingredient.v3", ingredient_assertion_cbor); |
| std::string actions_assertion_cbor = cbor::FromJson(absl::Substitute( |
| R"json( |
| { |
| "actions": [{ |
| "action": "c2pa.opened", |
| "parameters": { |
| "ingredients": [{ |
| "url": "self#jumbf=c2pa.assertions/c2pa.ingredient.v3", |
| "alg": "sha256", |
| "hash": "b64'$0'" |
| }] |
| } |
| }] |
| } |
| )json", |
| absl::Base64Escape(GetHash("sha256", ingredient_assertion_box)))); |
| |
| jumbf::SuperBox actions_assertion_box = |
| CreateAssertionBoxFromCbor("c2pa.actions.v2", actions_assertion_cbor); |
| jumbf::SuperBox update_assertion_store = CreateAssertionStoreBox( |
| {actions_assertion_box, ingredient_assertion_box}); |
| jumbf::SuperBox update_manifest = |
| CreateUpdateManifest(kManifestLabel2, {update_assertion_store}); |
| |
| jumbf::SuperBox manifest_store = |
| CreateManifestStore({std_manifest, update_manifest}); |
| Claim update_claim; |
| AddCreatedAssertion(&update_claim, actions_assertion_box); |
| AddCreatedAssertion(&update_claim, ingredient_assertion_box); |
| |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_is_update_manifest(true); |
| manifest.set_label(kManifestLabel2); |
| *manifest.mutable_claim() = update_claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailureStatuses(), IsEmpty()); |
| } |
| |
| TEST_F(AssertionValidatorTest, InvalidUpdateManifestMultipleParentIngredients) { |
| jumbf::SuperBox std_assertion_store_1 = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| jumbf::SuperBox std_assertion_store_2 = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| auto std_manifest_1 = |
| CreateStandardManifest(kManifestLabel1, {std_assertion_store_1}); |
| auto std_manifest_2 = |
| CreateStandardManifest(kManifestLabel2, {std_assertion_store_2}); |
| |
| std::string actions_assertion_cbor = cbor::FromJson(R"json( |
| { |
| "actions": [{"action": "c2pa.opened"}] |
| } |
| )json"); |
| |
| std::string ingredient_assertion_1_cbor = cbor::FromJson(absl::Substitute( |
| R"json( |
| { |
| "relationship": "parentOf", |
| "instanceID": "ingredient-instance-id-1", |
| "c2pa_manifest": { |
| "url": "self#jumbf=/c2pa/$0", |
| "alg": "sha256", |
| "hash": "b64'$1'" |
| } |
| } |
| )json", |
| kManifestLabel1, absl::Base64Escape(GetHash("sha256", std_manifest_1)))); |
| std::string ingredient_assertion_2_cbor = cbor::FromJson(absl::Substitute( |
| R"json( |
| { |
| "relationship": "parentOf", |
| "instanceID": "ingredient-instance-id-2", |
| "c2pa_manifest": { |
| "url": "self#jumbf=/c2pa/$0", |
| "alg": "sha256", |
| "hash": "b64'$1'" |
| } |
| } |
| )json", |
| kManifestLabel2, absl::Base64Escape(GetHash("sha256", std_manifest_2)))); |
| jumbf::SuperBox actions_assertion_box = |
| CreateAssertionBoxFromCbor("c2pa.actions.v2", actions_assertion_cbor); |
| jumbf::SuperBox ingredient_assertion_1_box = CreateAssertionBoxFromCbor( |
| "c2pa.ingredient.v3", ingredient_assertion_1_cbor); |
| jumbf::SuperBox ingredient_assertion_2_box = CreateAssertionBoxFromCbor( |
| "c2pa.ingredient.v3__1", ingredient_assertion_2_cbor); |
| jumbf::SuperBox update_assertion_store = CreateAssertionStoreBox({ |
| actions_assertion_box, |
| ingredient_assertion_1_box, |
| ingredient_assertion_2_box, |
| }); |
| jumbf::SuperBox update_manifest = |
| CreateUpdateManifest(kManifestLabel3, {update_assertion_store}); |
| |
| jumbf::SuperBox manifest_store = |
| CreateManifestStore({std_manifest_1, std_manifest_2, update_manifest}); |
| Claim update_claim; |
| AddCreatedAssertion(&update_claim, actions_assertion_box); |
| AddCreatedAssertion(&update_claim, ingredient_assertion_1_box); |
| AddCreatedAssertion(&update_claim, ingredient_assertion_2_box); |
| |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_is_update_manifest(true); |
| manifest.set_label(kManifestLabel3); |
| *manifest.mutable_claim() = update_claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kManifestUpdateWrongParents)); |
| } |
| |
| TEST_F(AssertionValidatorTest, InvalidUpdateManifestHardBindingsPresent) { |
| jumbf::SuperBox std_assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| auto std_manifest = |
| CreateStandardManifest(kManifestLabel1, {std_assertion_store}); |
| |
| std::string actions_assertion_cbor = cbor::FromJson(R"json( |
| { |
| "actions": [{"action": "c2pa.opened"}] |
| } |
| )json"); |
| |
| std::string ingredient_assertion_cbor = cbor::FromJson(absl::Substitute( |
| R"json( |
| { |
| "relationship": "parentOf", |
| "instanceID": "ingredient-instance-id", |
| "c2pa_manifest": { |
| "url": "self#jumbf=/c2pa/$0", |
| "alg": "sha256", |
| "hash": "b64'$1'" |
| } |
| } |
| )json", |
| kManifestLabel1, absl::Base64Escape(GetHash("sha256", std_manifest)))); |
| jumbf::SuperBox actions_assertion_box = |
| CreateAssertionBoxFromCbor("c2pa.actions.v2", actions_assertion_cbor); |
| jumbf::SuperBox ingredient_assertion_box = CreateAssertionBoxFromCbor( |
| "c2pa.ingredient.v3", ingredient_assertion_cbor); |
| jumbf::SuperBox binding_assertion_box = |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_); |
| jumbf::SuperBox update_assertion_store = CreateAssertionStoreBox({ |
| actions_assertion_box, |
| ingredient_assertion_box, |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| jumbf::SuperBox update_manifest = |
| CreateUpdateManifest(kManifestLabel2, {update_assertion_store}); |
| |
| jumbf::SuperBox manifest_store = |
| CreateManifestStore({std_manifest, update_manifest}); |
| Claim update_claim; |
| AddCreatedAssertion(&update_claim, actions_assertion_box); |
| AddCreatedAssertion(&update_claim, ingredient_assertion_box); |
| AddCreatedAssertion(&update_claim, binding_assertion_box); |
| |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_is_update_manifest(true); |
| manifest.set_label(kManifestLabel2); |
| *manifest.mutable_claim() = update_claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kManifestUpdateInvalid)); |
| } |
| |
| TEST_F(AssertionValidatorTest, InvalidUpdateManifestDisallowedAction) { |
| jumbf::SuperBox std_assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| auto std_manifest = |
| CreateStandardManifest(kManifestLabel1, {std_assertion_store}); |
| |
| // c2pa.cropped is not allowed in an update manifest. |
| std::string actions_assertion_cbor = FromJson(R"json( |
| { |
| "actions": [{"action": "c2pa.opened"}, {"action": "c2pa.cropped"}] |
| } |
| )json"); |
| |
| std::string ingredient_assertion_cbor = FromJson(absl::Substitute( |
| R"json( |
| { |
| "relationship": "parentOf", |
| "instanceID": "ingredient-instance-id", |
| "c2pa_manifest": { |
| "url": "self#jumbf=/c2pa/$0", |
| "alg": "sha256", |
| "hash": "b64'$1'" |
| } |
| } |
| )json", |
| kManifestLabel1, absl::Base64Escape(GetHash("sha256", std_manifest)))); |
| jumbf::SuperBox actions_assertion_box = |
| CreateAssertionBoxFromCbor("c2pa.actions.v2", actions_assertion_cbor); |
| jumbf::SuperBox ingredient_assertion_box = CreateAssertionBoxFromCbor( |
| "c2pa.ingredient.v3", ingredient_assertion_cbor); |
| jumbf::SuperBox update_assertion_store = CreateAssertionStoreBox( |
| {actions_assertion_box, ingredient_assertion_box}); |
| jumbf::SuperBox update_manifest = |
| CreateUpdateManifest(kManifestLabel2, {update_assertion_store}); |
| |
| jumbf::SuperBox manifest_store = |
| CreateManifestStore({std_manifest, update_manifest}); |
| Claim update_claim; |
| AddCreatedAssertion(&update_claim, actions_assertion_box); |
| AddCreatedAssertion(&update_claim, ingredient_assertion_box); |
| |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_is_update_manifest(true); |
| manifest.set_label(kManifestLabel2); |
| *manifest.mutable_claim() = update_claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kManifestUpdateInvalid)); |
| } |
| |
| TEST_F(AssertionValidatorTest, InvalidUpdateManifestNoIngredients) { |
| std::string actions_assertion_cbor = FromJson(R"json( |
| { |
| "actions": [{"action": "c2pa.opened"}] |
| } |
| )json"); |
| jumbf::SuperBox actions_assertion_box = |
| CreateAssertionBoxFromCbor("c2pa.actions.v2", actions_assertion_cbor); |
| jumbf::SuperBox update_assertion_store = |
| CreateAssertionStoreBox({actions_assertion_box}); |
| jumbf::SuperBox update_manifest = |
| CreateUpdateManifest(kManifestLabel2, {update_assertion_store}); |
| |
| jumbf::SuperBox manifest_store = CreateManifestStore({update_manifest}); |
| Claim update_claim; |
| AddCreatedAssertion(&update_claim, actions_assertion_box); |
| |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_is_update_manifest(true); |
| manifest.set_label(kManifestLabel2); |
| *manifest.mutable_claim() = update_claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kManifestUpdateWrongParents)); |
| } |
| |
| TEST_F(AssertionValidatorTest, InvalidUpdateManifestWrongIngredientRelation) { |
| jumbf::SuperBox std_assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| auto std_manifest = |
| CreateStandardManifest(kManifestLabel1, {std_assertion_store}); |
| |
| std::string actions_assertion_cbor = FromJson(R"json( |
| { |
| "actions": [{"action": "c2pa.opened"}] |
| } |
| )json"); |
| |
| // Relationship here is "componentOf" instead of "parentOf". |
| std::string ingredient_assertion_cbor = FromJson(absl::Substitute( |
| R"json( |
| { |
| "relationship": "componentOf", |
| "instanceID": "ingredient-instance-id", |
| "c2pa_manifest": { |
| "url": "self#jumbf=/c2pa/$0", |
| "alg": "sha256", |
| "hash": "b64'$1'" |
| } |
| } |
| )json", |
| kManifestLabel1, absl::Base64Escape(GetHash("sha256", std_manifest)))); |
| jumbf::SuperBox actions_assertion_box = |
| CreateAssertionBoxFromCbor("c2pa.actions.v2", actions_assertion_cbor); |
| jumbf::SuperBox ingredient_assertion_box = CreateAssertionBoxFromCbor( |
| "c2pa.ingredient.v3", ingredient_assertion_cbor); |
| jumbf::SuperBox update_assertion_store = CreateAssertionStoreBox( |
| {actions_assertion_box, ingredient_assertion_box}); |
| jumbf::SuperBox update_manifest = |
| CreateUpdateManifest(kManifestLabel2, {update_assertion_store}); |
| |
| jumbf::SuperBox manifest_store = |
| CreateManifestStore({std_manifest, update_manifest}); |
| Claim update_claim; |
| AddCreatedAssertion(&update_claim, actions_assertion_box); |
| AddCreatedAssertion(&update_claim, ingredient_assertion_box); |
| |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_is_update_manifest(true); |
| manifest.set_label(kManifestLabel2); |
| *manifest.mutable_claim() = update_claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kManifestUpdateWrongParents)); |
| } |
| |
| TEST_F(AssertionValidatorTest, IngredientV1Unsupported) { |
| Claim claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| std::string ingredient_assertion_cbor = FromJson(R"json( |
| { |
| "dc:title": "ingredient-title", |
| "dc:format": "ingredient-format", |
| "relationship": "componentOf", |
| "instanceID": "ingredient-instance-id", |
| "c2pa_manifest": { |
| "url": "self#jumbf=/c2pa/urn:uuid:AFAFAFAF-0101-0202-0303-ABCDEFFEDCBA", |
| "alg": "sha256", |
| "hash": "b64'EliDwzUVTZC6NXyCMndU824N4FKwkHxkx68nnsVWYfg='" |
| }, |
| "thumbnail": { |
| "url": "self#jumbf=/c2pa/urn:uuid:AFAFAFAF-0101-0202-0303-ABCDEFFEDCBA/c2pa.thumbnail.ingredient_1.jpg", |
| "alg": "sha256", |
| "hash": "b64'Jl/aF6NGEbFTPYooH/aA3FeRsM4KEcJbNeEcjnVoVQk='" |
| } |
| } |
| )json"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.ingredient", ingredient_assertion_cbor), |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| jumbf::SuperBox thumbnail_box = { |
| .description = |
| { |
| .label = "c2pa.thumbnail.ingredient_1.jpg", |
| }, |
| .raw_bytes = "b64'Zm9v'", |
| }; |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store}), |
| CreateStandardManifest(kManifestLabel2, |
| {assertion_store, thumbnail_box})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT( |
| t.GetFailureStatuses(), |
| Contains(PartiallyEqualsProto(ParseTextProtoOrDie<ValidationStatus>(R"pb( |
| code: "com.google.unsupportedSpecVersion" |
| explanation: "c2pa.ingredient (v1) and c2pa.ingredient.v2 assertions are not supported" |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, IngredientV2Unsupported) { |
| Claim claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v2" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| std::string ingredient_assertion_cbor = FromJson(R"json( |
| { |
| "dc:title": "ingredient-title", |
| "dc:format": "ingredient-format", |
| "relationship": "componentOf", |
| "c2pa_manifest": { |
| "url": "self#jumbf=/c2pa/urn:uuid:AFAFAFAF-0101-0202-0303-ABCDEFFEDCBA", |
| "alg": "sha256", |
| "hash": "b64'EliDwzUVTZC6NXyCMndU824N4FKwkHxkx68nnsVWYfg='" |
| }, |
| "thumbnail": { |
| "url": "self#jumbf=/c2pa/urn:uuid:AFAFAFAF-0101-0202-0303-ABCDEFFEDCBA/c2pa.thumbnail.ingredient_1.jpg", |
| "alg": "sha256", |
| "hash": "b64'Jl/aF6NGEbFTPYooH/aA3FeRsM4KEcJbNeEcjnVoVQk='" |
| } |
| } |
| )json"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.ingredient.v2", |
| ingredient_assertion_cbor), |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| jumbf::SuperBox thumbnail_box = { |
| .description = |
| { |
| .label = "c2pa.thumbnail.ingredient_1.jpg", |
| }, |
| .raw_bytes = "b64'Zm9v'", |
| }; |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store}), |
| CreateStandardManifest(kManifestLabel2, |
| {assertion_store, thumbnail_box})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT( |
| t.GetFailureStatuses(), |
| Contains(PartiallyEqualsProto(ParseTextProtoOrDie<ValidationStatus>(R"pb( |
| code: "com.google.unsupportedSpecVersion" |
| explanation: "c2pa.ingredient (v1) and c2pa.ingredient.v2 assertions are not supported" |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateIngredientV3) { |
| Claim claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| std::string ingredient_assertion_cbor = FromJson(R"json( |
| { |
| "dc:title": "ingredient-title", |
| "dc:format": "ingredient-format", |
| "relationship": "componentOf", |
| "activeManifest": { |
| "url": "self#jumbf=/c2pa/urn:uuid:AFAFAFAF-0101-0202-0303-ABCDEFFEDCBA", |
| "alg": "sha256", |
| "hash": "b64'EliDwzUVTZC6NXyCMndU824N4FKwkHxkx68nnsVWYfg='" |
| }, |
| "claimSignature": { |
| "url": "self#jumbf=/c2pa/urn:uuid:AFAFAFAF-0101-0202-0303-ABCDEFFEDCBA/c2pa.signature", |
| "alg": "sha256", |
| "hash": "b64'Jl/aF6NGEbFTPYooH/aA3FeRsM4KEcJbNeEcjnVoVQk='" |
| }, |
| "thumbnail": { |
| "url": "self#jumbf=/c2pa/urn:uuid:AFAFAFAF-0101-0202-0303-ABCDEFFEDCBA/c2pa.thumbnail.ingredient_1.jpg", |
| "alg": "sha256", |
| "hash": "b64'Jl/aF6NGEbFTPYooH/aA3FeRsM4KEcJbNeEcjnVoVQk='" |
| }, |
| "validationResults": { |
| "activeManifest": { |
| "success": [{"code": "assertion.dataHash.match"}], |
| "informational": [], |
| "failure": [] |
| } |
| } |
| } |
| )json"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.ingredient.v3", |
| ingredient_assertion_cbor), |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| jumbf::SuperBox thumbnail_box = { |
| .description = |
| { |
| .label = "c2pa.thumbnail.ingredient_1.jpg", |
| }, |
| .raw_bytes = "b64'Zm9v'", |
| }; |
| jumbf::SuperBox claim_signature_box = { |
| .description = |
| { |
| .label = "c2pa.signature", |
| }, |
| .raw_bytes = "b64'Zm9v'", |
| }; |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store}), |
| CreateStandardManifest(kManifestLabel2, {assertion_store, thumbnail_box, |
| claim_signature_box})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| ASSERT_THAT(t.GetFailureStatuses(), IsEmpty()); |
| EXPECT_THAT( |
| manifest.assertions(), |
| ElementsAre( |
| PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.hash.data" |
| data_hash {} |
| )pb")), |
| PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.ingredient.v3" |
| ingredient_v3 { |
| active_manifest { |
| url: "self#jumbf=/c2pa/urn:uuid:AFAFAFAF-0101-0202-0303-ABCDEFFEDCBA" |
| } |
| thumbnail { |
| url: "self#jumbf=/c2pa/urn:uuid:AFAFAFAF-0101-0202-0303-ABCDEFFEDCBA/c2pa.thumbnail.ingredient_1.jpg" |
| } |
| } |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateIngredientV3Failed) { |
| Claim claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| std::string ingredient_assertion_cbor = FromJson(R"json( |
| { |
| "dc:title": "ingredient-title", |
| "dc:format": "ingredient-format", |
| "relationship": "componentOf", |
| "activeManifest": { |
| "url": "self#jumbf=/c2pa/urn:uuid:AFAFAFAF-0101-0202-0303-ABCDEFFEDCBA", |
| "alg": "sha256", |
| "hash": "b64'Zm9v'" |
| }, |
| "thumbnail": { |
| "url": "self#jumbf=/c2pa/urn:uuid:AFAFAFAF-0101-0202-0303-ABCDEFFEDCBA/c2pa.thumbnail.ingredient_1.jpg", |
| "alg": "sha256", |
| "hash": "b64'BAD/HASH'" |
| }, |
| "validationResults": {} |
| } |
| )json"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.ingredient.v3", |
| ingredient_assertion_cbor), |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| jumbf::SuperBox thumbnail_box = { |
| .description = |
| { |
| .label = "c2pa.thumbnail.ingredient_1.jpg", |
| }, |
| .raw_bytes = "b64'Zm9v'", |
| }; |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store}), |
| CreateStandardManifest(kManifestLabel2, |
| {assertion_store, thumbnail_box})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kHashedUriMismatch)); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateIngredientV3FailedBadRelationship) { |
| Claim claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| std::string ingredient_assertion_cbor = FromJson(R"json( |
| { |
| "relationship": "someOtherRelationship" |
| } |
| )json"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.ingredient.v3", |
| ingredient_assertion_cbor), |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT( |
| t.GetFailureStatuses(), |
| Contains(PartiallyEqualsProto(ParseTextProtoOrDie<ValidationStatus>(R"pb( |
| code: "assertion.ingredient.malformed" |
| explanation: "Unrecognized ingredient relationship" |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateIngredientV3FailedMissingRelationship) { |
| Claim claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| std::string ingredient_assertion_cbor = FromJson(R"json({})json"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.ingredient.v3", |
| ingredient_assertion_cbor), |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT( |
| t.GetFailureStatuses(), |
| Contains(PartiallyEqualsProto(ParseTextProtoOrDie<ValidationStatus>(R"pb( |
| code: "assertion.ingredient.malformed" |
| explanation: "Missing ingredient relationship" |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, |
| ValidateIngredientV3FailedBothActiveManifestAndDigitalSourceType) { |
| Claim claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| std::string ingredient_assertion_cbor = FromJson(R"json( |
| { |
| "relationship": "componentOf", |
| "activeManifest": { |
| "url": "self#jumbf=c2pa.assertions/c2pa.ingredient.v3", |
| "alg": "sha256", |
| "hash": "b64'Zm9v'" |
| }, |
| "digitalSourceType": "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia" |
| } |
| )json"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.ingredient.v3", |
| ingredient_assertion_cbor), |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT( |
| t.GetFailureStatuses(), |
| Contains(PartiallyEqualsProto(ParseTextProtoOrDie<ValidationStatus>(R"pb( |
| code: "assertion.ingredient.malformed" |
| explanation: "Ingredient assertion must not contain both activeManifest and digitalSourceType" |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateIngredientV3FailedNoHardBindingSuccess) { |
| Claim claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| std::string ingredient_assertion_cbor = FromJson(R"json({ |
| "relationship": "componentOf", |
| "activeManifest": { |
| "url": "self#jumbf=/c2pa/urn:uuid:AFAFAFAF-0101-0202-0303-ABCDEFFEDCBA", |
| "alg": "sha256", |
| "hash": "b64'Zm9v'" |
| }, |
| "validationResults": { |
| "activeManifest": { |
| "success": [{"code": "assertion.hashedURI.match"}], |
| "informational": [], |
| "failure": [] |
| } |
| } |
| })json"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.ingredient.v3", |
| ingredient_assertion_cbor), |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT( |
| t.GetFailureStatuses(), |
| Contains(PartiallyEqualsProto(ParseTextProtoOrDie<ValidationStatus>(R"pb( |
| code: "assertion.ingredient.malformed" |
| explanation: "no hard binding check recorded" |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, |
| ValidateIngredientV3UpdateManifestFailedNoValidationResults) { |
| Claim claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| std::string ingredient_assertion_cbor = FromJson(R"json({ |
| "relationship": "parentOf", |
| "activeManifest": { |
| "url": "self#jumbf=/c2pa/urn:uuid:AFAFAFAF-0101-0202-0303-ABCDEFFEDCBA", |
| "alg": "sha256", |
| "hash": "b64'Zm9v'" |
| }, |
| "validationResults": {} |
| })json"); |
| jumbf::SuperBox assertion_store = |
| CreateAssertionStoreBox({CreateAssertionBoxFromCbor( |
| "c2pa.ingredient.v3", ingredient_assertion_cbor)}); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateUpdateManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| manifest.set_is_update_manifest(true); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT( |
| t.GetFailureStatuses(), |
| Contains(PartiallyEqualsProto(ParseTextProtoOrDie<ValidationStatus>(R"pb( |
| code: "assertion.ingredient.malformed" |
| explanation: "no validation successes or failures recorded" |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateActionsV2Ingredients) { |
| Claim claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.actions.v2" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3__1" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| std::string actions_assertion_cbor = FromJson(R"json( |
| { |
| "actions": [ |
| { |
| "action": "c2pa.created", |
| "parameters" : { |
| "ingredients" : [ |
| { |
| "url": "self#jumbf=c2pa.assertions/c2pa.ingredient.v3", |
| "alg": "sha256", |
| "hash" : "b64'EliDwzUVTZC6NXyCMndU824N4FKwkHxkx68nnsVWYfg='" |
| }, |
| { |
| "url": "self#jumbf=c2pa.assertions/c2pa.ingredient.v3__1", |
| "alg": "sha256", |
| "hash" : "b64'EliDwzUVTZC6NXyCMndU824N4FKwkHxkx68nnsVWYfg='" |
| } |
| ] |
| } |
| } |
| ] |
| } |
| )json"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.actions.v2", actions_assertion_cbor), |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| CreateAssertionBoxFromCbor("c2pa.ingredient.v3", |
| ingredient_assertion_cbor_), |
| CreateAssertionBoxFromCbor("c2pa.ingredient.v3__1", |
| ingredient_assertion_cbor_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| ASSERT_THAT(t.GetFailureStatuses(), IsEmpty()); |
| EXPECT_THAT( |
| manifest.assertions(), |
| ElementsAre( |
| PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.hash.data" |
| data_hash {} |
| )pb")), |
| PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.actions.v2" |
| actions { |
| actions { |
| action: "c2pa.created" |
| parameters { |
| ingredients { |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" |
| } |
| ingredients { |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3__1" |
| } |
| } |
| } |
| } |
| )pb")), |
| PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.ingredient.v3" |
| )pb")), |
| PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.ingredient.v3__1" |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidateActionsV2IngredientsFailed) { |
| Claim claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.actions.v2" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient__1" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient__2" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| std::string actions_assertion_cbor = FromJson(R"json( |
| { |
| "actions": [ |
| { |
| "action": "c2pa.created", |
| "parameters" : { |
| "ingredients" : [ |
| { |
| "url": "self#jumbf=c2pa.assertions/c2pa.ingredient__1", |
| "alg": "sha256", |
| "hash" : "b64'EliDwzUVTZC6NXyCMndU824N4FKwkHxkx68nnsVWYfg='" |
| }, |
| { |
| "url": "self#jumbf=c2pa.assertions/c2pa.ingredient__2", |
| "alg": "sha256", |
| "hash" : "b64'Zm9v'" |
| } |
| ] |
| } |
| } |
| ] |
| } |
| )json"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.actions.v2", actions_assertion_cbor), |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| CreateAssertionBoxFromCbor("c2pa.ingredient__1", |
| ingredient_assertion_cbor_), |
| CreateAssertionBoxFromCbor("c2pa.ingredient__2", |
| ingredient_assertion_cbor_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kHashedUriMismatch)); |
| } |
| |
| TEST_F(AssertionValidatorTest, MissingAssertionFailure) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.metadata" |
| algorithm: "sha256" |
| hash: "hash-is-irrelevant" |
| } |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kAssertionMissing)); |
| EXPECT_THAT(manifest.assertions(), SizeIs(1)); |
| } |
| |
| TEST_F(AssertionValidatorTest, MissingRedactedAssertionOk) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.metadata" |
| algorithm: "sha256" |
| hash: "hash-is-irrelevant" |
| } |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| std::string redacted_assertion_path = absl::Substitute( |
| "/c2pa/$0/c2pa.assertions/c2pa.metadata", kManifestLabel1); |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions( |
| uri_resolver, /*redacted_assertion_paths=*/{redacted_assertion_path}, |
| manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailureStatuses(), IsEmpty()); |
| EXPECT_THAT(manifest.assertions(), SizeIs(2)); |
| EXPECT_THAT(manifest.assertions(), |
| Contains(PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.hash.data" |
| data_hash {} |
| )pb")))); |
| EXPECT_THAT(manifest.assertions(), |
| Contains(PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.metadata" |
| redacted_by_generator {} |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, RedactedAssertionWithZeroJsonDataOk) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.metadata" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| CreateAssertionBoxFromJson("c2pa.metadata", "\0\0\0"), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| std::string redacted_assertion_path = absl::Substitute( |
| "/c2pa/$0/c2pa.assertions/c2pa.metadata", kManifestLabel1); |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions( |
| uri_resolver, /*redacted_assertion_paths=*/{redacted_assertion_path}, |
| manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailureStatuses(), IsEmpty()); |
| EXPECT_THAT(manifest.assertions(), SizeIs(2)); |
| EXPECT_THAT(manifest.assertions(), |
| Contains(PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.metadata" |
| redacted_by_generator {} |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, RedactedAssertionWithNonzeroJsonDataFails) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.metadata" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| CreateAssertionBoxFromJson("c2pa.metadata", metadata_json_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| std::string redacted_assertion_path = absl::Substitute( |
| "/c2pa/$0/c2pa.assertions/c2pa.metadata", kManifestLabel1); |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions( |
| uri_resolver, /*redacted_assertion_paths=*/{redacted_assertion_path}, |
| manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kAssertionNotRedacted)); |
| EXPECT_THAT(manifest.assertions(), SizeIs(1)); |
| } |
| |
| TEST_F(AssertionValidatorTest, RedactedAssertionWithZeroCborDataOk) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" |
| algorithm: "sha256" |
| hash: "hash-is-irrelevant" |
| } |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| CreateAssertionBoxFromCbor("c2pa.ingredient.v3", "\0\0\0"), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| std::string redacted_assertion_path = absl::Substitute( |
| "/c2pa/$0/c2pa.assertions/c2pa.ingredient.v3", kManifestLabel1); |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions( |
| uri_resolver, /*redacted_assertion_paths=*/{redacted_assertion_path}, |
| manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailureStatuses(), IsEmpty()); |
| EXPECT_THAT(manifest.assertions(), SizeIs(2)); |
| EXPECT_THAT(manifest.assertions(), |
| Contains(PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.ingredient.v3" |
| redacted_by_generator {} |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, RedactedAssertionWithNonZeroCborDataFails) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| CreateAssertionBoxFromCbor("c2pa.ingredient.v3", |
| ingredient_assertion_cbor_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| std::string redacted_assertion_path = absl::Substitute( |
| "/c2pa/$0/c2pa.assertions/c2pa.ingredient.v3", kManifestLabel1); |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions( |
| uri_resolver, /*redacted_assertion_paths=*/{redacted_assertion_path}, |
| manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kAssertionNotRedacted)); |
| EXPECT_THAT(manifest.assertions(), SizeIs(1)); |
| } |
| |
| TEST_F(AssertionValidatorTest, RedactedAssertionWithZeroEmbeddedFileDataOk) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.thumbnail.claim.png" |
| algorithm: "sha256" |
| hash: "hash-is-irrelevant" |
| } |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| CreateAssertionBoxFromEmbeddedFile("c2pa.thumbnail.claim.png", |
| "image/png", "\0\0\0", |
| "claim.thumbnail.png"), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| std::string redacted_assertion_path = absl::Substitute( |
| "/c2pa/$0/c2pa.assertions/c2pa.thumbnail.claim.png", kManifestLabel1); |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions( |
| uri_resolver, /*redacted_assertion_paths=*/{redacted_assertion_path}, |
| manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailureStatuses(), IsEmpty()); |
| EXPECT_THAT(manifest.assertions(), SizeIs(2)); |
| EXPECT_THAT(manifest.assertions(), |
| Contains(PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.thumbnail.claim.png" |
| redacted_by_generator {} |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, |
| RedactedAssertionWithNonZeroEmbeddedFileDataFails) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.thumbnail.claim.png" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| CreateAssertionBoxFromEmbeddedFile("c2pa.thumbnail.claim.png", |
| "image/png", embedded_file_, |
| "claim.thumbnail.png"), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| std::string redacted_assertion_path = absl::Substitute( |
| "/c2pa/$0/c2pa.assertions/c2pa.thumbnail.claim.png", kManifestLabel1); |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions( |
| uri_resolver, /*redacted_assertion_paths=*/{redacted_assertion_path}, |
| manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kAssertionNotRedacted)); |
| EXPECT_THAT(manifest.assertions(), SizeIs(1)); |
| } |
| |
| TEST_F(AssertionValidatorTest, MissingIconUriFails) { |
| Claim claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| claim_generator_info { |
| name: "generator_icon" |
| icon { algorithm: "sha256" hash: "hash-is-irrelevant" } |
| } |
| label: "c2pa.claim.v2" |
| )pb"); |
| jumbf::SuperBox assertion_store = |
| CreateAssertionStoreBox({CreateAssertionBoxFromCbor( |
| "c2pa.hash.data", data_hash_assertion_cbor_)}); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT( |
| t.GetFailureStatuses(), |
| ElementsAre(EqualsProto(ParseTextProtoOrDie<ValidationStatus>(R"pb( |
| code: "hashedUri.missing" |
| url: "self#jumbf=/c2pa/urn:uuid:1234/c2pa.claim.v2" |
| explanation: "Icon URI is missing" |
| )pb")))); |
| EXPECT_THAT(manifest.assertions(), |
| ElementsAre(EqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.hash.data" |
| data_hash { |
| exclusions { start: 9960 length: 4213 } |
| exclusions { start: 14173 length: 1000 } |
| alg: "sha256" |
| hash: "foo" |
| name: "JUMBF manifest" |
| } |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, ExternalIconUriFails) { |
| Claim claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| claim_generator_info { |
| name: "generator_icon" |
| icon { |
| url: "https://example.com/icon.png" |
| algorithm: "sha256" |
| hash: "hash-is-irrelevant" |
| } |
| } |
| label: "c2pa.claim.v2" |
| )pb"); |
| jumbf::SuperBox assertion_store = |
| CreateAssertionStoreBox({CreateAssertionBoxFromCbor( |
| "c2pa.hash.data", data_hash_assertion_cbor_)}); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT( |
| t.GetFailureStatuses(), |
| ElementsAre(EqualsProto(ParseTextProtoOrDie<ValidationStatus>(R"pb( |
| code: "com.google.hashedUri.unsupported" |
| url: "self#jumbf=/c2pa/urn:uuid:1234/c2pa.claim.v2" |
| explanation: "Icon URI is pointing to an external resource" |
| )pb")))); |
| EXPECT_THAT(manifest.assertions(), |
| ElementsAre(EqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.hash.data" |
| data_hash { |
| exclusions { start: 9960 length: 4213 } |
| exclusions { start: 14173 length: 1000 } |
| alg: "sha256" |
| hash: "foo" |
| name: "JUMBF manifest" |
| } |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, IconUriPointsToMissingLocationFails) { |
| Claim claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| claim_generator_info { |
| name: "generator_icon" |
| icon { |
| url: "self#jumbf=c2pa.missing" |
| algorithm: "sha256" |
| hash: "hash-is-irrelevant" |
| } |
| } |
| label: "c2pa.claim.v2" |
| )pb"); |
| jumbf::SuperBox assertion_store = |
| CreateAssertionStoreBox({CreateAssertionBoxFromCbor( |
| "c2pa.hash.data", data_hash_assertion_cbor_)}); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT( |
| t.GetFailureStatuses(), |
| ElementsAre(EqualsProto(ParseTextProtoOrDie<ValidationStatus>(R"pb( |
| code: "hashedUri.missing" |
| url: "self#jumbf=/c2pa/urn:uuid:1234/c2pa.claim.v2" |
| )pb")))); |
| EXPECT_THAT(manifest.assertions(), |
| ElementsAre(EqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.hash.data" |
| data_hash { |
| exclusions { start: 9960 length: 4213 } |
| exclusions { start: 14173 length: 1000 } |
| alg: "sha256" |
| hash: "foo" |
| name: "JUMBF manifest" |
| } |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidIconUri) { |
| Claim claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| claim_generator_info { |
| name: "generator_icon" |
| icon { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| } |
| label: "c2pa.claim.v2" |
| )pb"); |
| jumbf::SuperBox assertion_store = |
| CreateAssertionStoreBox({CreateAssertionBoxFromCbor( |
| "c2pa.hash.data", data_hash_assertion_cbor_)}); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailureStatuses(), IsEmpty()); |
| EXPECT_THAT(manifest.assertions(), |
| ElementsAre(EqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.hash.data" |
| data_hash { |
| exclusions { start: 9960 length: 4213 } |
| exclusions { start: 14173 length: 1000 } |
| alg: "sha256" |
| hash: "foo" |
| name: "JUMBF manifest" |
| } |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, ValidIconUriFallbacksToDefaultAlgorithm) { |
| Claim claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| claim_generator_info { |
| name: "generator_icon" |
| icon { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| } |
| default_algorithm: "sha256" |
| label: "c2pa.claim.v2" |
| )pb"); |
| jumbf::SuperBox assertion_store = |
| CreateAssertionStoreBox({CreateAssertionBoxFromCbor( |
| "c2pa.hash.data", data_hash_assertion_cbor_)}); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailureStatuses(), IsEmpty()); |
| EXPECT_THAT(manifest.assertions(), |
| ElementsAre(EqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( |
| label: "c2pa.hash.data" |
| data_hash { |
| exclusions { start: 9960 length: 4213 } |
| exclusions { start: 14173 length: 1000 } |
| alg: "sha256" |
| hash: "foo" |
| name: "JUMBF manifest" |
| } |
| )pb")))); |
| } |
| |
| TEST_F(AssertionValidatorTest, FailsWhenMultipleHardBindings) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data__1" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| CreateAssertionBoxFromCbor("c2pa.hash.data__1", |
| data_hash_assertion_cbor_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT( |
| t.GetFailures(), |
| ContainsFailure(FailureStatusCode::kAssertionMultipleHardBindings)); |
| EXPECT_THAT(manifest.assertions(), SizeIs(2)); |
| } |
| |
| TEST_F(AssertionValidatorTest, |
| PassesWhenAdditionalHardAssertionsArePartsOfMultiAssetHash) { |
| auto claim = ParseTextProtoOrDie<Claim>(R"pb( |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data.part" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| created_assertions { |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.data" |
| algorithm: "sha256" |
| hash: "\x12\x58\x83\xc3\x35\x15\x4d\x90\xba\x35\x7c\x82\x32\x77\x54\xf3\x6e\x0d\xe0\x52\xb0\x90\x7c\x64\xc7\xaf\x27\x9e\xc5\x56\x61\xf8" |
| } |
| )pb"); |
| jumbf::SuperBox assertion_store = CreateAssertionStoreBox({ |
| CreateAssertionBoxFromCbor("c2pa.hash.data.part", |
| data_hash_assertion_cbor_), |
| CreateAssertionBoxFromCbor("c2pa.hash.data", data_hash_assertion_cbor_), |
| }); |
| jumbf::SuperBox manifest_store = CreateManifestStore( |
| {CreateStandardManifest(kManifestLabel1, {assertion_store})}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| AssertionValidator validator; |
| TestValidationTracker t; |
| |
| Manifest manifest; |
| manifest.set_label(kManifestLabel1); |
| *manifest.mutable_claim() = claim; |
| validator.ValidateClaimAssertions(uri_resolver, {}, manifest, t.tracker()); |
| |
| EXPECT_THAT(t.GetFailures(), IsEmpty()); |
| EXPECT_THAT(manifest.assertions(), SizeIs(2)); |
| } |
| |
| } // namespace |
| } // namespace credentio |