| // 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 |