| // 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 "validator/graph.h" |
| |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include "absl/container/flat_hash_set.h" |
| #include "absl/status/status.h" |
| #include "absl/status/status_macros.h" // IWYU pragma: keep |
| #include "absl/status/status_matchers.h" |
| #include "absl/status/statusor.h" |
| #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 "assertion/validator.h" |
| #include "constants/status_codes.h" |
| #include "crypto/algorithms.h" |
| #include "crypto/default/hasher.h" |
| #include "crypto/hash.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include "jumbf/box.h" |
| #include "jumbf/uri.h" |
| #include "proto/assertion.pb.h" |
| #include "proto/data_hash_assertion.pb.h" |
| #include "proto/hashed_uri.pb.h" |
| #include "proto/ingredient_assertion.pb.h" |
| #include "proto/manifest.pb.h" |
| #include "proto/validation_result.pb.h" |
| #include "testing/fake_assertion_validator.h" |
| #include "testing/fake_claim_validator.h" |
| #include "testing/jumbf_utils.h" |
| #include "testing/mock_assertion_validator.h" |
| #include "testing/proto_test_utils.h" |
| #include "validator/tracker.h" |
| #include "validator/validator_options.h" |
| |
| namespace credentio { |
| namespace { |
| |
| using ::credentio_testing::EqualsProto; |
| using ::credentio_testing::ParseTextProtoOrDie; |
| using ::testing::_; |
| using ::testing::Contains; |
| using ::testing::IsEmpty; |
| using ::testing::Property; |
| using ::testing::SizeIs; |
| using ::testing::UnorderedElementsAre; |
| |
| class NoopHashChecker : public HashChecker { |
| public: |
| void Update(absl::string_view data) override {} |
| bool Check(absl::string_view hash) override { return true; } |
| |
| private: |
| absl::string_view hash_value_; |
| }; |
| |
| class NoopHashCheckerFactory : public HashCheckerFactory { |
| public: |
| absl::StatusOr<std::unique_ptr<HashChecker>> Create( |
| HashAlgorithm algorithm) const override { |
| return std::make_unique<NoopHashChecker>(); |
| } |
| }; |
| |
| Claim DefaultClaim() { |
| Claim claim; |
| claim.set_default_algorithm("sha256"); |
| return claim; |
| } |
| |
| class ManifestGraphTest : public ::testing::Test { |
| protected: |
| ManifestGraphTest() |
| : claim_signature_box_bytes_( |
| EncodeClaimSignatureBox("claim-signature-cbor")), |
| claim_signature_box_(ParseSuperBoxOrDie(&claim_signature_box_bytes_)), |
| empty_data_hash_assertion_(ParseTextProtoOrDie<Assertion>( |
| "label: 'c2pa.hash.data' data_hash {}")) {} |
| |
| std::string claim_signature_box_bytes_; |
| jumbf::SuperBox claim_signature_box_; |
| NoopHashCheckerFactory noop_hash_checker_factory_; |
| ValidatorOptions options_; |
| Assertion empty_data_hash_assertion_; |
| }; |
| |
| TEST_F(ManifestGraphTest, ClaimValidationFails) { |
| FakeClaimValidator claim_validator({FailureStatusCode::kGeneralError}, |
| /*informationals=*/{}, /*successes=*/{}); |
| auto assertion_validator = std::make_unique<AssertionValidator>(); |
| jumbf::SuperBox root_box; |
| jumbf::SuperBox active_manifest = CreateStandardManifest("urn:c2pa:1", {}); |
| auto uri_resolver = jumbf::UriResolver::WithSingleRootChild(&root_box); |
| ManifestGraph graph(&active_manifest, &uri_resolver, |
| assertion_validator.get(), &claim_validator, |
| &noop_hash_checker_factory_, &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| EXPECT_THAT(**result, |
| EqualsProto(ParseTextProtoOrDie<PartialValidationResultProto>( |
| R"pb(active_manifest { |
| label: "urn:c2pa:1" |
| validation { |
| failures { |
| code: "general.error" |
| url: "self#jumbf=/c2pa/urn:c2pa:1" |
| } |
| } |
| } |
| )pb"))); |
| } |
| |
| TEST_F(ManifestGraphTest, StandardManifestAccepted) { |
| FakeClaimValidator claim_validator( |
| /*failures=*/{}, /*informationals=*/{}, |
| /*successes=*/{SuccessStatusCode::kClaimSignatureValidated}); |
| claim_validator.SetClaimResult("urn:c2pa:1", DefaultClaim()); |
| FakeAssertionValidator assertion_validator(/*failures=*/{}, /*successes=*/{}); |
| jumbf::SuperBox root_box; |
| std::vector<jumbf::SuperBox> manifests = { |
| CreateStandardManifest("urn:c2pa:1", {})}; |
| assertion_validator.SetOutputAssertions("/c2pa/urn:c2pa:1", |
| {empty_data_hash_assertion_}); |
| jumbf::SuperBox manifest_store = CreateManifestStore(manifests); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| ManifestGraph graph(&manifests[0], &uri_resolver, &assertion_validator, |
| &claim_validator, &noop_hash_checker_factory_, &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| EXPECT_THAT(**result, EqualsProto(ParseTextProtoOrDie< |
| PartialValidationResultProto>(R"pb( |
| active_manifest { |
| label: "urn:c2pa:1" |
| validation { |
| successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:1" |
| } |
| } |
| claim { default_algorithm: "sha256" } |
| assertions { |
| label: "c2pa.hash.data" |
| data_hash {} |
| } |
| } |
| hard_binding_uri: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.assertions/c2pa.hash.data" |
| )pb"))); |
| } |
| |
| TEST_F(ManifestGraphTest, PopulatesMultiAssetHashUri) { |
| FakeClaimValidator claim_validator( |
| /*failures=*/{}, /*informationals=*/{}, |
| /*successes=*/{SuccessStatusCode::kClaimSignatureValidated}); |
| claim_validator.SetClaimResult("urn:c2pa:1", DefaultClaim()); |
| FakeAssertionValidator assertion_validator(/*failures=*/{}, /*successes=*/{}); |
| jumbf::SuperBox root_box; |
| std::vector<jumbf::SuperBox> manifests = { |
| CreateStandardManifest("urn:c2pa:1", {})}; |
| |
| Assertion multi_asset_hash_assertion = ParseTextProtoOrDie<Assertion>( |
| "label: 'c2pa.hash.multi-asset' multi_asset_hash {}"); |
| Assertion part0_hash_assertion = ParseTextProtoOrDie<Assertion>( |
| "label: 'c2pa.hash.data.part' data_hash {}"); |
| Assertion part1_hash_assertion = ParseTextProtoOrDie<Assertion>( |
| "label: 'c2pa.hash.data.part__1' data_hash {}"); |
| |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:1", |
| {empty_data_hash_assertion_, multi_asset_hash_assertion, |
| part0_hash_assertion, part1_hash_assertion}); |
| jumbf::SuperBox manifest_store = CreateManifestStore(manifests); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| ManifestGraph graph(&manifests[0], &uri_resolver, &assertion_validator, |
| &claim_validator, &noop_hash_checker_factory_, &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| EXPECT_THAT(**result, EqualsProto(ParseTextProtoOrDie< |
| PartialValidationResultProto>(R"pb( |
| active_manifest { |
| label: "urn:c2pa:1" |
| validation { |
| successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:1" |
| } |
| } |
| claim { default_algorithm: "sha256" } |
| assertions { |
| label: "c2pa.hash.data" |
| data_hash {} |
| } |
| assertions { |
| label: "c2pa.hash.multi-asset" |
| multi_asset_hash {} |
| } |
| assertions { |
| label: "c2pa.hash.data.part" |
| data_hash {} |
| } |
| assertions { |
| label: "c2pa.hash.data.part__1" |
| data_hash {} |
| } |
| } |
| hard_binding_uri: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.assertions/c2pa.hash.data" |
| multi_asset_hash_uri: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.assertions/c2pa.hash.multi-asset" |
| )pb"))); |
| } |
| |
| TEST_F(ManifestGraphTest, PopulatesHardBindingAndMultiAssetHashUris) { |
| FakeClaimValidator claim_validator( |
| /*failures=*/{}, /*informationals=*/{}, |
| /*successes=*/{SuccessStatusCode::kClaimSignatureValidated}); |
| claim_validator.SetClaimResult("urn:c2pa:1", DefaultClaim()); |
| FakeAssertionValidator assertion_validator(/*failures=*/{}, /*successes=*/{}); |
| jumbf::SuperBox root_box; |
| std::vector<jumbf::SuperBox> manifests = { |
| CreateStandardManifest("urn:c2pa:1", {})}; |
| |
| Assertion part1_data_hash_assertion = ParseTextProtoOrDie<Assertion>( |
| "label: 'c2pa.hash.data.part' data_hash {}"); |
| Assertion part2_boxes_hash_assertion = ParseTextProtoOrDie<Assertion>( |
| "label: 'c2pa.hash.boxes.part' boxes_hash {}"); |
| |
| Assertion multi_asset_hash_assertion = ParseTextProtoOrDie<Assertion>( |
| "label: 'c2pa.hash.multi-asset' multi_asset_hash {}"); |
| |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:1", |
| {part1_data_hash_assertion, part2_boxes_hash_assertion, |
| multi_asset_hash_assertion, empty_data_hash_assertion_}); |
| jumbf::SuperBox manifest_store = CreateManifestStore(manifests); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| ManifestGraph graph(&manifests[0], &uri_resolver, &assertion_validator, |
| &claim_validator, &noop_hash_checker_factory_, &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| EXPECT_THAT(**result, EqualsProto(ParseTextProtoOrDie< |
| PartialValidationResultProto>(R"pb( |
| active_manifest { |
| label: "urn:c2pa:1" |
| validation { |
| successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:1" |
| } |
| } |
| claim { default_algorithm: "sha256" } |
| assertions { |
| label: "c2pa.hash.data.part" |
| data_hash {} |
| } |
| assertions { |
| label: "c2pa.hash.boxes.part" |
| boxes_hash {} |
| } |
| assertions { |
| label: "c2pa.hash.multi-asset" |
| multi_asset_hash {} |
| } |
| assertions { |
| label: "c2pa.hash.data" |
| data_hash {} |
| } |
| } |
| hard_binding_uri: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.assertions/c2pa.hash.data" |
| multi_asset_hash_uri: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.assertions/c2pa.hash.multi-asset" |
| )pb"))); |
| } |
| |
| TEST_F(ManifestGraphTest, IngredientStandardManifestAccepted) { |
| FakeClaimValidator claim_validator( |
| /*failures=*/{}, /*informationals=*/{}, |
| /*successes=*/{SuccessStatusCode::kClaimSignatureValidated}); |
| claim_validator.SetClaimResult("urn:c2pa:1", DefaultClaim()); |
| claim_validator.SetClaimResult("urn:c2pa:2", DefaultClaim()); |
| FakeAssertionValidator assertion_validator(/*failures=*/{}, /*successes=*/{}); |
| jumbf::SuperBox root_box; |
| std::vector<jumbf::SuperBox> manifests = { |
| CreateStandardManifest("urn:c2pa:1", {claim_signature_box_}), |
| CreateStandardManifest("urn:c2pa:2", {}), |
| }; |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:2", |
| {empty_data_hash_assertion_, |
| ParseTextProtoOrDie<Assertion>( |
| R"pb( |
| label: "c2pa.ingredient.v3" |
| ingredient_v3 { |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:1" } |
| claim_signature { |
| url: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.signature" |
| } |
| })pb")}); |
| jumbf::SuperBox manifest_store = CreateManifestStore(manifests); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| ManifestGraph graph(&manifests[1], &uri_resolver, &assertion_validator, |
| &claim_validator, &noop_hash_checker_factory_, &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| // claimSignature.validated contains the "wrong" URL as it's added by the |
| // FakeClaimValidator. |
| EXPECT_THAT(**result, EqualsProto(ParseTextProtoOrDie< |
| PartialValidationResultProto>(R"pb( |
| active_manifest { |
| label: "urn:c2pa:2" |
| validation { |
| successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:2" |
| } |
| successes { |
| code: "ingredient.claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:2/c2pa.assertions/c2pa.ingredient.v3" |
| } |
| } |
| claim { default_algorithm: "sha256" } |
| assertions { |
| label: "c2pa.hash.data" |
| data_hash {} |
| } |
| assertions { |
| label: "c2pa.ingredient.v3" |
| ingredient_v3 { |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:1" } |
| claim_signature { url: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.signature" } |
| } |
| } |
| } |
| ingredient_manifests { |
| label: "urn:c2pa:1" |
| validation { |
| successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:1" |
| } |
| } |
| claim { default_algorithm: "sha256" } |
| } |
| hard_binding_uri: "self#jumbf=/c2pa/urn:c2pa:2/c2pa.assertions/c2pa.hash.data" |
| )pb"))); |
| } |
| |
| TEST_F(ManifestGraphTest, UpdateManifestAccepted) { |
| FakeClaimValidator claim_validator( |
| /*failures=*/{}, /*informationals=*/{}, |
| /*successes=*/{SuccessStatusCode::kClaimSignatureValidated}); |
| claim_validator.SetClaimResult("urn:c2pa:1", DefaultClaim()); |
| claim_validator.SetClaimResult("urn:c2pa:2", DefaultClaim()); |
| FakeAssertionValidator assertion_validator(/*failures=*/{}, /*successes=*/{}); |
| jumbf::SuperBox root_box; |
| auto standard_manifest = |
| CreateStandardManifest("urn:c2pa:1", {claim_signature_box_}); |
| auto update_manifest = CreateUpdateManifest("urn:c2pa:2", {}); |
| assertion_validator.SetOutputAssertions("/c2pa/urn:c2pa:1", |
| {empty_data_hash_assertion_}); |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:2", |
| {ParseTextProtoOrDie<Assertion>( |
| R"pb( |
| label: "c2pa.ingredient.v3" |
| ingredient_v3 { |
| relationship: "parentOf" |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:1" } |
| claim_signature { |
| url: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.signature" |
| } |
| })pb")}); |
| std::vector<jumbf::SuperBox> manifests = {standard_manifest, update_manifest}; |
| jumbf::SuperBox manifest_store = CreateManifestStore(manifests); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| ManifestGraph graph(&manifests[1], &uri_resolver, &assertion_validator, |
| &claim_validator, &noop_hash_checker_factory_, &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| EXPECT_THAT(**result, EqualsProto(ParseTextProtoOrDie< |
| PartialValidationResultProto>(R"pb( |
| active_manifest { |
| label: "urn:c2pa:2" |
| is_update_manifest: true |
| claim { default_algorithm: "sha256" } |
| assertions { |
| label: "c2pa.ingredient.v3" |
| ingredient_v3 { |
| relationship: "parentOf" |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:1" } |
| claim_signature { url: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.signature" } |
| } |
| } |
| validation { |
| successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:2" |
| } |
| successes { |
| code: "ingredient.claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:2/c2pa.assertions/c2pa.ingredient.v3" |
| } |
| } |
| } |
| ingredient_manifests { |
| label: "urn:c2pa:1" |
| claim { default_algorithm: "sha256" } |
| assertions { |
| label: "c2pa.hash.data" |
| data_hash {} |
| } |
| validation { |
| successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:1" |
| } |
| } |
| } |
| hard_binding_uri: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.assertions/c2pa.hash.data" |
| )pb"))); |
| } |
| |
| TEST_F(ManifestGraphTest, |
| StandardManifestWithIngredientUpdateManifestAccepted) { |
| FakeClaimValidator claim_validator( |
| /*failures=*/{}, /*informationals=*/{}, |
| /*successes=*/{SuccessStatusCode::kClaimSignatureValidated}); |
| claim_validator.SetClaimResult("urn:c2pa:IngredientStandard", DefaultClaim()); |
| claim_validator.SetClaimResult("urn:c2pa:IngredientUpdate", DefaultClaim()); |
| claim_validator.SetClaimResult("urn:c2pa:ActiveStandard", DefaultClaim()); |
| FakeAssertionValidator assertion_validator(/*failures=*/{}, /*successes=*/{}); |
| jumbf::SuperBox root_box; |
| auto standard_manifest = CreateStandardManifest("urn:c2pa:IngredientStandard", |
| {claim_signature_box_}); |
| auto update_manifest = |
| CreateUpdateManifest("urn:c2pa:IngredientUpdate", {claim_signature_box_}); |
| auto active_manifest = CreateStandardManifest("urn:c2pa:ActiveStandard", {}); |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:IngredientUpdate", |
| {ParseTextProtoOrDie<Assertion>( |
| R"pb( |
| label: "c2pa.ingredient.v3" |
| ingredient_v3 { |
| relationship: "parentOf" |
| active_manifest { |
| url: "self#jumbf=/c2pa/urn:c2pa:IngredientStandard" |
| } |
| claim_signature { |
| url: "self#jumbf=/c2pa/urn:c2pa:IngredientStandard/c2pa.signature" |
| } |
| })pb")}); |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:ActiveStandard", |
| {empty_data_hash_assertion_, |
| ParseTextProtoOrDie<Assertion>( |
| R"pb( |
| label: "c2pa.ingredient.v3" |
| ingredient_v3 { |
| relationship: "parentOf" |
| active_manifest { |
| url: "self#jumbf=/c2pa/urn:c2pa:IngredientUpdate" |
| } |
| claim_signature { |
| url: "self#jumbf=/c2pa/urn:c2pa:IngredientUpdate/c2pa.signature" |
| } |
| })pb")}); |
| std::vector<jumbf::SuperBox> manifests = {standard_manifest, update_manifest, |
| active_manifest}; |
| jumbf::SuperBox manifest_store = CreateManifestStore(manifests); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| ManifestGraph graph(&manifests[2], &uri_resolver, &assertion_validator, |
| &claim_validator, &noop_hash_checker_factory_, &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| EXPECT_THAT(**result, EqualsProto(ParseTextProtoOrDie< |
| PartialValidationResultProto>(R"pb( |
| active_manifest { |
| label: "urn:c2pa:ActiveStandard" |
| validation { |
| successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:ActiveStandard" |
| } |
| successes { |
| code: "ingredient.claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:ActiveStandard/c2pa.assertions/c2pa.ingredient.v3" |
| } |
| } |
| claim { default_algorithm: "sha256" } |
| assertions { |
| label: "c2pa.hash.data" |
| data_hash {} |
| } |
| assertions { |
| label: "c2pa.ingredient.v3" |
| ingredient_v3 { |
| relationship: "parentOf" |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:IngredientUpdate" } |
| claim_signature { |
| url: "self#jumbf=/c2pa/urn:c2pa:IngredientUpdate/c2pa.signature" |
| } |
| } |
| } |
| } |
| ingredient_manifests { |
| label: "urn:c2pa:IngredientUpdate" |
| is_update_manifest: true |
| claim { default_algorithm: "sha256" } |
| assertions { |
| label: "c2pa.ingredient.v3" |
| ingredient_v3 { |
| relationship: "parentOf" |
| active_manifest { |
| url: "self#jumbf=/c2pa/urn:c2pa:IngredientStandard" |
| } |
| claim_signature { |
| url: "self#jumbf=/c2pa/urn:c2pa:IngredientStandard/c2pa.signature" |
| } |
| } |
| } |
| validation { |
| successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:IngredientUpdate" |
| } |
| successes { |
| code: "ingredient.claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:IngredientUpdate/c2pa.assertions/c2pa.ingredient.v3" |
| } |
| } |
| } |
| ingredient_manifests { |
| label: "urn:c2pa:IngredientStandard" |
| claim { default_algorithm: "sha256" } |
| validation { |
| successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:IngredientStandard" |
| } |
| } |
| } |
| hard_binding_uri: "self#jumbf=/c2pa/urn:c2pa:ActiveStandard/c2pa.assertions/c2pa.hash.data" |
| )pb"))); |
| } |
| |
| TEST_F(ManifestGraphTest, IngredientManifestV3UrlIsInvalid) { |
| FakeClaimValidator claim_validator(/*failures=*/{}, /*informationals=*/{}, |
| /*successes=*/{}); |
| claim_validator.SetClaimResult("urn:c2pa:1", DefaultClaim()); |
| FakeAssertionValidator assertion_validator(/*failures=*/{}, /*successes=*/{}); |
| jumbf::SuperBox root_box; |
| jumbf::SuperBox active_manifest = CreateStandardManifest("urn:c2pa:1", {}); |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:1", {empty_data_hash_assertion_, |
| ParseTextProtoOrDie<Assertion>( |
| R"pb(label: "ingredient_v3" |
| ingredient_v3 { |
| active_manifest { url: "invalid_url" } |
| })pb")}); |
| auto uri_resolver = jumbf::UriResolver::WithSingleRootChild(&root_box); |
| ManifestGraph graph(&active_manifest, &uri_resolver, &assertion_validator, |
| &claim_validator, &noop_hash_checker_factory_, &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| EXPECT_THAT(**result, EqualsProto(ParseTextProtoOrDie< |
| PartialValidationResultProto>(R"pb( |
| active_manifest { |
| label: "urn:c2pa:1" |
| claim { default_algorithm: "sha256" } |
| assertions { |
| label: "c2pa.hash.data" |
| data_hash {} |
| } |
| assertions { |
| label: "ingredient_v3" |
| ingredient_v3 { active_manifest { url: "invalid_url" } } |
| } |
| validation { |
| failures { |
| code: "ingredient.manifest.missing" |
| url: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.assertions/ingredient_v3" |
| } |
| } |
| } |
| hard_binding_uri: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.assertions/c2pa.hash.data" |
| )pb"))); |
| } |
| |
| TEST_F(ManifestGraphTest, IngredientV3ClaimSignatureHashMatch) { |
| FakeClaimValidator claim_validator( |
| /*failures=*/{}, /*informationals=*/{}, |
| /*successes=*/{SuccessStatusCode::kClaimSignatureValidated}); |
| claim_validator.SetClaimResult("urn:c2pa:1", DefaultClaim()); |
| claim_validator.SetClaimResult("urn:c2pa:2", DefaultClaim()); |
| FakeAssertionValidator assertion_validator(/*failures=*/{}, /*successes=*/{}); |
| jumbf::SuperBox root_box; |
| std::vector<jumbf::SuperBox> manifests = { |
| CreateStandardManifest("urn:c2pa:1", {claim_signature_box_}), |
| CreateStandardManifest("urn:c2pa:2", {}), |
| }; |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:2", |
| {empty_data_hash_assertion_, |
| ParseTextProtoOrDie<Assertion>(absl::Substitute( |
| R"pb( |
| label: "c2pa.ingredient.v3" |
| ingredient_v3 { |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:1" } |
| claim_signature { |
| url: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.signature" |
| hash: "$0" |
| algorithm: "sha256" |
| } |
| })pb", |
| absl::CEscape(GetHash("sha256", claim_signature_box_))))}); |
| jumbf::SuperBox manifest_store = CreateManifestStore(manifests); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| ManifestGraph graph(&manifests[1], &uri_resolver, &assertion_validator, |
| &claim_validator, &DefaultHashCheckerFactory(), |
| &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| EXPECT_THAT((*result)->active_manifest().validation().failures(), IsEmpty()); |
| EXPECT_THAT( |
| (*result)->active_manifest().validation().successes(), |
| Contains(EqualsProto(ParseTextProtoOrDie<ValidationStatus>(R"pb( |
| code: "ingredient.claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:2/c2pa.assertions/c2pa.ingredient.v3" |
| )pb")))); |
| ASSERT_THAT((*result)->ingredient_manifests(), SizeIs(1)); |
| EXPECT_THAT((*result)->ingredient_manifests(0).validation().failures(), |
| IsEmpty()); |
| } |
| |
| TEST_F(ManifestGraphTest, IngredientV3ClaimSignatureHashMismatch) { |
| FakeClaimValidator claim_validator( |
| /*failures=*/{}, /*informationals=*/{}, |
| /*successes=*/{SuccessStatusCode::kClaimSignatureValidated}); |
| claim_validator.SetClaimResult("urn:c2pa:1", DefaultClaim()); |
| claim_validator.SetClaimResult("urn:c2pa:2", DefaultClaim()); |
| FakeAssertionValidator assertion_validator(/*failures=*/{}, /*successes=*/{}); |
| jumbf::SuperBox root_box; |
| std::vector<jumbf::SuperBox> manifests = { |
| CreateStandardManifest("urn:c2pa:1", {claim_signature_box_}), |
| CreateStandardManifest("urn:c2pa:2", {}), |
| }; |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:2", |
| {empty_data_hash_assertion_, |
| ParseTextProtoOrDie<Assertion>( |
| R"pb(label: "ingredient_v3" |
| ingredient_v3 { |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:1" } |
| claim_signature { |
| url: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.signature" |
| hash: "wrong hash" |
| algorithm: "sha256" |
| } |
| })pb")}); |
| jumbf::SuperBox manifest_store = CreateManifestStore(manifests); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| ManifestGraph graph(&manifests[1], &uri_resolver, &assertion_validator, |
| &claim_validator, &DefaultHashCheckerFactory(), |
| &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| EXPECT_THAT((*result)->active_manifest().validation().failures(), |
| Contains(EqualsProto(ParseTextProtoOrDie<ValidationStatus>(R"pb( |
| code: "ingredient.claimSignature.mismatch" |
| url: "self#jumbf=/c2pa/urn:c2pa:2/c2pa.assertions/ingredient_v3" |
| )pb")))); |
| EXPECT_THAT((*result)->ingredient_manifests(), IsEmpty()); |
| } |
| |
| TEST_F(ManifestGraphTest, IngredientManifestHashMatch) { |
| FakeClaimValidator claim_validator( |
| /*failures=*/{}, /*informationals=*/{}, |
| /*successes=*/{SuccessStatusCode::kClaimSignatureValidated}); |
| claim_validator.SetClaimResult("urn:c2pa:1", DefaultClaim()); |
| claim_validator.SetClaimResult("urn:c2pa:2", DefaultClaim()); |
| FakeAssertionValidator assertion_validator(/*failures=*/{}, /*successes=*/{}); |
| jumbf::SuperBox root_box; |
| std::vector<jumbf::SuperBox> manifests = { |
| CreateStandardManifest("urn:c2pa:1", {claim_signature_box_}), |
| CreateStandardManifest("urn:c2pa:2", {}), |
| }; |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:2", |
| {empty_data_hash_assertion_, |
| ParseTextProtoOrDie<Assertion>(absl::Substitute( |
| R"pb( |
| label: "c2pa.ingredient.v3" |
| ingredient_v3 { |
| active_manifest { |
| url: "self#jumbf=/c2pa/urn:c2pa:1" |
| hash: "$0" |
| algorithm: "sha256" |
| } |
| })pb", |
| absl::CEscape(GetHash("sha256", manifests[0]))))}); |
| jumbf::SuperBox manifest_store = CreateManifestStore(manifests); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| ManifestGraph graph(&manifests[1], &uri_resolver, &assertion_validator, |
| &claim_validator, &DefaultHashCheckerFactory(), |
| &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| EXPECT_THAT((*result)->active_manifest().validation().failures(), IsEmpty()); |
| EXPECT_THAT( |
| (*result)->active_manifest().validation().successes(), |
| Contains(EqualsProto(ParseTextProtoOrDie<ValidationStatus>(R"pb( |
| code: "ingredient.manifest.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:2/c2pa.assertions/c2pa.ingredient.v3" |
| )pb")))); |
| ASSERT_THAT((*result)->ingredient_manifests(), SizeIs(1)); |
| EXPECT_THAT((*result)->ingredient_manifests(0).validation().failures(), |
| IsEmpty()); |
| } |
| |
| TEST_F(ManifestGraphTest, IngredientManifestHashMismatch) { |
| FakeClaimValidator claim_validator( |
| /*failures=*/{}, /*informationals=*/{}, |
| /*successes=*/{SuccessStatusCode::kClaimSignatureValidated}); |
| claim_validator.SetClaimResult("urn:c2pa:1", DefaultClaim()); |
| claim_validator.SetClaimResult("urn:c2pa:2", DefaultClaim()); |
| FakeAssertionValidator assertion_validator(/*failures=*/{}, /*successes=*/{}); |
| jumbf::SuperBox root_box; |
| std::vector<jumbf::SuperBox> manifests = { |
| CreateStandardManifest("urn:c2pa:1", {claim_signature_box_}), |
| CreateStandardManifest("urn:c2pa:2", {}), |
| }; |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:2", {empty_data_hash_assertion_, |
| ParseTextProtoOrDie<Assertion>( |
| R"pb(label: "c2pa.ingredient.v3" |
| ingredient_v3 { |
| active_manifest { |
| url: "self#jumbf=/c2pa/urn:c2pa:1" |
| hash: "wrong hash" |
| algorithm: "sha256" |
| } |
| })pb")}); |
| jumbf::SuperBox manifest_store = CreateManifestStore(manifests); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| ManifestGraph graph(&manifests[1], &uri_resolver, &assertion_validator, |
| &claim_validator, &DefaultHashCheckerFactory(), |
| &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| EXPECT_THAT( |
| (*result)->active_manifest().validation().failures(), |
| Contains(EqualsProto(ParseTextProtoOrDie<ValidationStatus>(R"pb( |
| code: "ingredient.manifest.mismatch" |
| url: "self#jumbf=/c2pa/urn:c2pa:2/c2pa.assertions/c2pa.ingredient.v3" |
| )pb")))); |
| EXPECT_THAT((*result)->ingredient_manifests(), IsEmpty()); |
| } |
| |
| TEST_F(ManifestGraphTest, ManifestLegacyLabel) { |
| FakeClaimValidator claim_validator(/*failures=*/{}, /*informationals=*/{}, |
| /*successes=*/{}); |
| claim_validator.SetClaimResult("urn:uuid:some_label", DefaultClaim()); |
| FakeAssertionValidator assertion_validator(/*failures=*/{}, /*successes=*/{}); |
| jumbf::SuperBox root_box, active_manifest; |
| active_manifest.description.label = "urn:uuid:some_label"; |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:uuid:some_label", |
| {empty_data_hash_assertion_, |
| ParseTextProtoOrDie<Assertion>( |
| R"pb(ingredient_v3 { |
| active_manifest { |
| url: "self#jumbf=/c2pa/urn:uuid:some_label" |
| } |
| })pb")}); |
| auto uri_resolver = jumbf::UriResolver::WithSingleRootChild(&root_box); |
| ManifestGraph graph(&active_manifest, &uri_resolver, &assertion_validator, |
| &claim_validator, &noop_hash_checker_factory_, &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| EXPECT_THAT( |
| **result, |
| EqualsProto(ParseTextProtoOrDie<PartialValidationResultProto>(R"pb( |
| active_manifest { |
| label: "urn:uuid:some_label" |
| validation { |
| failures { |
| code: "com.google.unsupportedSpecVersion" |
| url: "self#jumbf=/c2pa/urn:uuid:some_label" |
| explanation: "deprecated manifest label format" |
| } |
| } |
| } |
| )pb"))); |
| } |
| |
| TEST_F(ManifestGraphTest, IngredientManifestUrlValidButManifestMissing) { |
| FakeClaimValidator claim_validator(/*failures=*/{}, /*informationals=*/{}, |
| /*successes=*/{}); |
| claim_validator.SetClaimResult("urn:c2pa:some_label", DefaultClaim()); |
| FakeAssertionValidator assertion_validator(/*failures=*/{}, /*successes=*/{}); |
| jumbf::SuperBox root_box; |
| jumbf::SuperBox active_manifest = |
| CreateStandardManifest("urn:c2pa:some_label", {}); |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:some_label", |
| {empty_data_hash_assertion_, |
| ParseTextProtoOrDie<Assertion>( |
| R"pb(label: "ingredient_v3" |
| ingredient_v3 { |
| active_manifest { |
| url: "self#jumbf=/c2pa/urn:c2pa:some_label" |
| } |
| })pb")}); |
| auto uri_resolver = jumbf::UriResolver::WithSingleRootChild(&root_box); |
| ManifestGraph graph(&active_manifest, &uri_resolver, &assertion_validator, |
| &claim_validator, &noop_hash_checker_factory_, &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| EXPECT_THAT(**result, EqualsProto(ParseTextProtoOrDie< |
| PartialValidationResultProto>(R"pb( |
| active_manifest { |
| label: "urn:c2pa:some_label" |
| claim { default_algorithm: "sha256" } |
| assertions { |
| label: "c2pa.hash.data" |
| data_hash {} |
| } |
| assertions { |
| label: "ingredient_v3" |
| ingredient_v3 { |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:some_label" } |
| } |
| } |
| validation { |
| failures { |
| code: "ingredient.manifest.missing" |
| url: "self#jumbf=/c2pa/urn:c2pa:some_label/c2pa.assertions/ingredient_v3" |
| } |
| } |
| } |
| hard_binding_uri: "self#jumbf=/c2pa/urn:c2pa:some_label/c2pa.assertions/c2pa.hash.data" |
| )pb"))); |
| } |
| |
| TEST_F(ManifestGraphTest, IngredientManifestV3SelfRedacted) { |
| FakeClaimValidator claim_validator(/*failures=*/{}, /*informationals=*/{}, |
| /*successes=*/{}); |
| Claim claim = DefaultClaim(); |
| claim.add_redacted_assertions("self#jumbf=self_redacted"); |
| claim_validator.SetClaimResult("urn:c2pa:1", claim); |
| FakeAssertionValidator assertion_validator(/*failures=*/{}, /*successes=*/{}); |
| jumbf::SuperBox root_box; |
| jumbf::SuperBox active_manifest = CreateStandardManifest("urn:c2pa:1", {}); |
| auto uri_resolver = jumbf::UriResolver::WithSingleRootChild(&root_box); |
| ManifestGraph graph(&active_manifest, &uri_resolver, &assertion_validator, |
| &claim_validator, &noop_hash_checker_factory_, &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| EXPECT_THAT( |
| **result, |
| EqualsProto(ParseTextProtoOrDie<PartialValidationResultProto>(R"pb( |
| active_manifest { |
| label: "urn:c2pa:1" |
| claim { |
| redacted_assertions: "self#jumbf=self_redacted" |
| default_algorithm: "sha256" |
| } |
| validation { |
| failures { |
| code: "assertion.selfRedacted" |
| url: "self#jumbf=/c2pa/urn:c2pa:1" |
| } |
| } |
| } |
| )pb"))); |
| } |
| |
| TEST_F(ManifestGraphTest, IngredientManifestV3InvalidRedactedAssertionUri) { |
| FakeClaimValidator claim_validator(/*failures=*/{}, /*informationals=*/{}, |
| /*successes=*/{}); |
| Claim claim = DefaultClaim(); |
| claim.add_redacted_assertions("invalid_uri"); |
| claim_validator.SetClaimResult("urn:c2pa:1", claim); |
| FakeAssertionValidator assertion_validator(/*failures=*/{}, /*successes=*/{}); |
| jumbf::SuperBox root_box; |
| jumbf::SuperBox active_manifest = CreateStandardManifest("urn:c2pa:1", {}); |
| auto uri_resolver = jumbf::UriResolver::WithSingleRootChild(&root_box); |
| ManifestGraph graph(&active_manifest, &uri_resolver, &assertion_validator, |
| &claim_validator, &noop_hash_checker_factory_, &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| EXPECT_THAT( |
| **result, |
| EqualsProto(ParseTextProtoOrDie<PartialValidationResultProto>(R"pb( |
| active_manifest { |
| label: "urn:c2pa:1" |
| claim { |
| redacted_assertions: "invalid_uri" |
| default_algorithm: "sha256" |
| } |
| validation { |
| failures { |
| code: "assertion.missing" |
| url: "self#jumbf=/c2pa/urn:c2pa:1" |
| } |
| } |
| } |
| )pb"))); |
| } |
| |
| TEST_F(ManifestGraphTest, IngredientManifestV3InvalidRedactedAssertionPath) { |
| FakeClaimValidator claim_validator(/*failures=*/{}, /*informationals=*/{}, |
| /*successes=*/{}); |
| Claim claim = DefaultClaim(); |
| claim.add_redacted_assertions("self#jumbf=/invalid_path"); |
| claim_validator.SetClaimResult("urn:c2pa:1", claim); |
| FakeAssertionValidator assertion_validator(/*failures=*/{}, /*successes=*/{}); |
| jumbf::SuperBox root_box; |
| jumbf::SuperBox active_manifest = CreateStandardManifest("urn:c2pa:1", {}); |
| auto uri_resolver = jumbf::UriResolver::WithSingleRootChild(&root_box); |
| ManifestGraph graph(&active_manifest, &uri_resolver, &assertion_validator, |
| &claim_validator, &noop_hash_checker_factory_, &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| EXPECT_THAT( |
| **result, |
| EqualsProto(ParseTextProtoOrDie<PartialValidationResultProto>(R"pb( |
| active_manifest { |
| label: "urn:c2pa:1" |
| claim { |
| redacted_assertions: "self#jumbf=/invalid_path" |
| default_algorithm: "sha256" |
| } |
| validation { |
| failures { |
| code: "assertion.missing" |
| url: "self#jumbf=/c2pa/urn:c2pa:1" |
| } |
| } |
| } |
| )pb"))); |
| } |
| |
| TEST_F(ManifestGraphTest, RedactedAssertionsProvidedToAssertionValidator) { |
| // Manifest graph: |
| // urn:c2pa:4 (active) has ingredients urn:c2pa:2, urn:c2pa:3 |
| // unn:c2pa:2 has ingredient urn:c2pa:1 |
| // urn:c2pa:3 has ingredient urn:c2pa:1 |
| FakeClaimValidator claim_validator( |
| /*failures=*/{}, /*informationals=*/{}, |
| /*successes=*/{SuccessStatusCode::kClaimSignatureValidated}); |
| Claim claim1 = DefaultClaim(); |
| Claim claim2 = ParseTextProtoOrDie<Claim>(R"pb( |
| default_algorithm: "sha256" |
| redacted_assertions: "self#jumbf=/c2pa/urn:c2pa:1/claim1_assertion_redacted_by_claim2" |
| )pb"); |
| Claim claim3 = ParseTextProtoOrDie<Claim>(R"pb( |
| default_algorithm: "sha256" |
| redacted_assertions: "self#jumbf=/c2pa/urn:c2pa:1/claim1_assertion_redacted_by_claim3" |
| )pb"); |
| Claim claim4 = ParseTextProtoOrDie<Claim>(R"pb( |
| default_algorithm: "sha256" |
| redacted_assertions: "self#jumbf=/c2pa/urn:c2pa:1/claim1_assertion_redacted_by_claim4" |
| redacted_assertions: "self#jumbf=/c2pa/urn:c2pa:2/claim2_assertion_redacted_by_claim4" |
| redacted_assertions: "self#jumbf=/c2pa/urn:c2pa:3/claim3_assertion_redacted_by_claim4" |
| )pb"); |
| claim_validator.SetClaimResult("urn:c2pa:1", claim1); |
| claim_validator.SetClaimResult("urn:c2pa:2", claim2); |
| claim_validator.SetClaimResult("urn:c2pa:3", claim3); |
| claim_validator.SetClaimResult("urn:c2pa:4", claim4); |
| MockAssertionValidator assertion_validator; |
| jumbf::SuperBox root_box; |
| std::vector<jumbf::SuperBox> manifests; |
| manifests.push_back(CreateStandardManifest("urn:c2pa:1", {})); |
| manifests.push_back(CreateStandardManifest("urn:c2pa:2", {})); |
| manifests.push_back(CreateStandardManifest("urn:c2pa:3", {})); |
| manifests.push_back(CreateStandardManifest("urn:c2pa:4", {})); |
| EXPECT_CALL(assertion_validator, |
| ValidateClaimAssertions( |
| _, _, Property(&Manifest::label, "urn:c2pa:1"), _)) |
| .WillOnce( |
| [&](const jumbf::UriResolver&, |
| const absl::flat_hash_set<std::string>& redacted_assertion_paths, |
| Manifest& manifest, ValidationTracker&) { |
| EXPECT_THAT( |
| redacted_assertion_paths, |
| // Note that the assertion redacted by claim3 does not appear |
| // here, because we haven't reached claim3 yet in the DFS |
| // when we process claim1. See discussion in |
| // https://github.com/c2pa-org/specs-core/issues/1540. |
| UnorderedElementsAre( |
| "/c2pa/urn:c2pa:1/claim1_assertion_redacted_by_claim4", |
| "/c2pa/urn:c2pa:1/claim1_assertion_redacted_by_claim2")); |
| }); |
| EXPECT_CALL(assertion_validator, |
| ValidateClaimAssertions( |
| _, _, Property(&Manifest::label, "urn:c2pa:2"), _)) |
| .WillOnce( |
| [&](const jumbf::UriResolver&, |
| const absl::flat_hash_set<std::string>& redacted_assertion_paths, |
| Manifest& manifest, ValidationTracker&) { |
| EXPECT_THAT( |
| redacted_assertion_paths, |
| UnorderedElementsAre( |
| "/c2pa/urn:c2pa:2/claim2_assertion_redacted_by_claim4")); |
| auto* active_manifest = manifest.add_assertions() |
| ->mutable_ingredient_v3() |
| ->mutable_active_manifest(); |
| active_manifest->set_url("self#jumbf=/c2pa/urn:c2pa:1"); |
| }); |
| EXPECT_CALL(assertion_validator, |
| ValidateClaimAssertions( |
| _, _, Property(&Manifest::label, "urn:c2pa:3"), _)) |
| .WillOnce( |
| [&](const jumbf::UriResolver&, |
| const absl::flat_hash_set<std::string>& redacted_assertion_paths, |
| Manifest& manifest, ValidationTracker&) { |
| EXPECT_THAT( |
| redacted_assertion_paths, |
| UnorderedElementsAre( |
| "/c2pa/urn:c2pa:3/claim3_assertion_redacted_by_claim4")); |
| auto* active_manifest = manifest.add_assertions() |
| ->mutable_ingredient_v3() |
| ->mutable_active_manifest(); |
| active_manifest->set_url("self#jumbf=/c2pa/urn:c2pa:1"); |
| }); |
| EXPECT_CALL(assertion_validator, |
| ValidateClaimAssertions( |
| _, _, Property(&Manifest::label, "urn:c2pa:4"), _)) |
| .WillOnce( |
| [&](const jumbf::UriResolver&, |
| const absl::flat_hash_set<std::string>& redacted_assertion_paths, |
| Manifest& manifest, ValidationTracker&) { |
| EXPECT_THAT(redacted_assertion_paths, IsEmpty()); |
| auto* active_manifest1 = manifest.add_assertions() |
| ->mutable_ingredient_v3() |
| ->mutable_active_manifest(); |
| active_manifest1->set_url("self#jumbf=/c2pa/urn:c2pa:2"); |
| auto* active_manifest2 = manifest.add_assertions() |
| ->mutable_ingredient_v3() |
| ->mutable_active_manifest(); |
| active_manifest2->set_url("self#jumbf=/c2pa/urn:c2pa:3"); |
| }); |
| |
| jumbf::SuperBox manifest_store = CreateManifestStore(manifests); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| ManifestGraph graph(&manifests.back(), &uri_resolver, &assertion_validator, |
| &claim_validator, &noop_hash_checker_factory_, &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| } |
| |
| TEST_F(ManifestGraphTest, IngredientNesting) { |
| FakeClaimValidator claim_validator( |
| /*failures=*/{}, /*informationals=*/{}, |
| /*successes=*/{SuccessStatusCode::kClaimSignatureValidated}); |
| claim_validator.SetClaimResult("urn:c2pa:1", DefaultClaim()); |
| claim_validator.SetClaimResult("urn:c2pa:2", DefaultClaim()); |
| claim_validator.SetClaimResult("urn:c2pa:3", DefaultClaim()); |
| claim_validator.SetClaimResult("urn:c2pa:4", DefaultClaim()); |
| claim_validator.SetClaimResult("urn:c2pa:5", DefaultClaim()); |
| claim_validator.SetClaimResult("urn:c2pa:6", DefaultClaim()); |
| |
| FakeAssertionValidator assertion_validator(/*failures=*/{}, /*successes=*/{}); |
| jumbf::SuperBox root_box; |
| std::vector<jumbf::SuperBox> manifests = { |
| CreateStandardManifest("urn:c2pa:1", {claim_signature_box_}), |
| CreateStandardManifest("urn:c2pa:2", {claim_signature_box_}), |
| CreateStandardManifest("urn:c2pa:3", {claim_signature_box_}), |
| CreateStandardManifest("urn:c2pa:4", {claim_signature_box_}), |
| CreateStandardManifest("urn:c2pa:5", {claim_signature_box_}), |
| CreateStandardManifest("urn:c2pa:6", {claim_signature_box_}), |
| }; |
| for (int i = 2; i <= 6; ++i) { |
| assertion_validator.SetOutputAssertions( |
| absl::StrCat("/c2pa/urn:c2pa:", i), |
| {empty_data_hash_assertion_, |
| ParseTextProtoOrDie<Assertion>(absl::Substitute( |
| R"pb( |
| label: "c2pa.ingredient.v3" |
| ingredient_v3 { |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:$0" } |
| claim_signature { |
| url: "self#jumbf=/c2pa/urn:c2pa:$0/c2pa.signature" |
| } |
| })pb", |
| i - 1))}); |
| } |
| jumbf::SuperBox manifest_store = CreateManifestStore(manifests); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| |
| ManifestGraph graph(&manifests[5], &uri_resolver, &assertion_validator, |
| &claim_validator, &noop_hash_checker_factory_, &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| EXPECT_THAT((*result)->active_manifest().validation().failures(), IsEmpty()); |
| ASSERT_THAT((*result)->ingredient_manifests(), SizeIs(5)); |
| EXPECT_THAT((*result)->ingredient_manifests(0).validation().failures(), |
| IsEmpty()); |
| EXPECT_THAT((*result)->ingredient_manifests(1).validation().failures(), |
| IsEmpty()); |
| EXPECT_THAT((*result)->ingredient_manifests(2).validation().failures(), |
| IsEmpty()); |
| EXPECT_THAT((*result)->ingredient_manifests(3).validation().failures(), |
| IsEmpty()); |
| EXPECT_THAT((*result)->ingredient_manifests(4).validation().failures(), |
| IsEmpty()); |
| } |
| |
| TEST_F(ManifestGraphTest, GraphContainsLoop) { |
| FakeClaimValidator claim_validator( |
| /*failures=*/{}, /*informationals=*/{}, |
| /*successes=*/{SuccessStatusCode::kClaimSignatureValidated}); |
| claim_validator.SetClaimResult("urn:c2pa:active_manifest", DefaultClaim()); |
| claim_validator.SetClaimResult("urn:c2pa:ingredient_manifest", |
| DefaultClaim()); |
| FakeAssertionValidator assertion_validator(/*failures=*/{}, /*successes=*/{}); |
| jumbf::SuperBox root_box; |
| jumbf::SuperBox active_manifest = CreateStandardManifest( |
| "urn:c2pa:active_manifest", {claim_signature_box_}); |
| jumbf::SuperBox ingredient_manifest = CreateStandardManifest( |
| "urn:c2pa:ingredient_manifest", {claim_signature_box_}); |
| jumbf::SuperBox manifest_store = |
| CreateManifestStore({ingredient_manifest, active_manifest}); |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:active_manifest", |
| {empty_data_hash_assertion_, |
| ParseTextProtoOrDie<Assertion>( |
| R"pb(ingredient_v3 { |
| active_manifest { |
| url: "self#jumbf=/c2pa/urn:c2pa:ingredient_manifest" |
| } |
| claim_signature { |
| url: "self#jumbf=/c2pa/urn:c2pa:ingredient_manifest/c2pa.signature" |
| } |
| })pb")}); |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:ingredient_manifest", |
| {empty_data_hash_assertion_, |
| ParseTextProtoOrDie<Assertion>( |
| R"pb(ingredient_v3 { |
| active_manifest { |
| url: "self#jumbf=/c2pa/urn:c2pa:active_manifest" |
| } |
| claim_signature { |
| url: "self#jumbf=/c2pa/urn:c2pa:active_manifest/c2pa.signature" |
| } |
| })pb")}); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| ManifestGraph graph(&active_manifest, &uri_resolver, &assertion_validator, |
| &claim_validator, &noop_hash_checker_factory_, &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| EXPECT_THAT((*result)->active_manifest().validation().failures(), IsEmpty()); |
| ASSERT_THAT((*result)->ingredient_manifests(), SizeIs(1)); |
| EXPECT_THAT((*result)->ingredient_manifests(0).validation().failures(), |
| IsEmpty()); |
| } |
| |
| TEST_F(ManifestGraphTest, IngredientV3WithInvalidClaimSignatureUri) { |
| FakeClaimValidator claim_validator( |
| /*failures=*/{}, /*informationals=*/{}, |
| /*successes=*/{SuccessStatusCode::kClaimSignatureValidated}); |
| claim_validator.SetClaimResult("urn:c2pa:1", DefaultClaim()); |
| claim_validator.SetClaimResult("urn:c2pa:2", DefaultClaim()); |
| FakeAssertionValidator assertion_validator(/*failures=*/{}, /*successes=*/{}); |
| jumbf::SuperBox root_box; |
| std::vector<jumbf::SuperBox> manifests; |
| manifests.push_back(CreateStandardManifest("urn:c2pa:1", {})); |
| manifests.push_back(CreateStandardManifest("urn:c2pa:2", {})); |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:1", |
| {empty_data_hash_assertion_, |
| ParseTextProtoOrDie<Assertion>( |
| R"pb(label: "ingredient_v3" |
| ingredient_v3 { |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:2" } |
| claim_signature { url: "invalid_uri" } |
| })pb")}); |
| jumbf::SuperBox manifest_store = CreateManifestStore(manifests); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| ManifestGraph graph(&manifests[0], &uri_resolver, &assertion_validator, |
| &claim_validator, &noop_hash_checker_factory_, &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| EXPECT_THAT(**result, EqualsProto(ParseTextProtoOrDie< |
| PartialValidationResultProto>(R"pb( |
| active_manifest { |
| label: "urn:c2pa:1" |
| validation { |
| successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:1" |
| } |
| failures { |
| code: "ingredient.claimSignature.missing" |
| url: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.assertions/ingredient_v3" |
| explanation: "self#jumbf= prefix required" |
| } |
| } |
| claim { default_algorithm: "sha256" } |
| assertions { |
| label: "c2pa.hash.data" |
| data_hash {} |
| } |
| assertions { |
| label: "ingredient_v3" |
| ingredient_v3 { |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:2" } |
| claim_signature { url: "invalid_uri" } |
| } |
| } |
| } |
| hard_binding_uri: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.assertions/c2pa.hash.data" |
| )pb"))); |
| } |
| |
| TEST_F(ManifestGraphTest, IngredientV3WithoutManifest) { |
| FakeClaimValidator claim_validator( |
| /*failures=*/{}, /*informationals=*/{}, |
| /*successes=*/{SuccessStatusCode::kClaimSignatureValidated}); |
| claim_validator.SetClaimResult("urn:c2pa:1", DefaultClaim()); |
| FakeAssertionValidator assertion_validator(/*failures=*/{}, /*successes=*/{}); |
| jumbf::SuperBox root_box; |
| std::vector<jumbf::SuperBox> manifests; |
| manifests.push_back(CreateStandardManifest("urn:c2pa:1", {})); |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:1", |
| {empty_data_hash_assertion_, |
| ParseTextProtoOrDie<Assertion>( |
| R"pb(label: "ingredient_v3" |
| ingredient_v3 { |
| relationship: "componentOf" |
| claim_signature { |
| url: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.signature" |
| } |
| })pb")}); |
| jumbf::SuperBox manifest_store = CreateManifestStore(manifests); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| ManifestGraph graph(&manifests[0], &uri_resolver, &assertion_validator, |
| &claim_validator, &noop_hash_checker_factory_, &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| EXPECT_THAT(**result, EqualsProto(ParseTextProtoOrDie< |
| PartialValidationResultProto>(R"pb( |
| active_manifest { |
| label: "urn:c2pa:1" |
| claim { default_algorithm: "sha256" } |
| assertions { |
| label: "c2pa.hash.data" |
| data_hash {} |
| } |
| assertions { |
| label: "ingredient_v3" |
| ingredient_v3 { |
| relationship: "componentOf" |
| claim_signature { url: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.signature" } |
| } |
| } |
| validation { |
| informationals { |
| code: "ingredient.unknownProvenance" |
| url: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.assertions/ingredient_v3" |
| } |
| successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:1" |
| } |
| } |
| } |
| hard_binding_uri: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.assertions/c2pa.hash.data" |
| )pb"))); |
| } |
| |
| TEST_F(ManifestGraphTest, IngredientV3InputToWithoutManifest) { |
| FakeClaimValidator claim_validator( |
| /*failures=*/{}, /*informationals=*/{}, |
| /*successes=*/{SuccessStatusCode::kClaimSignatureValidated}); |
| claim_validator.SetClaimResult("urn:c2pa:1", DefaultClaim()); |
| FakeAssertionValidator assertion_validator(/*failures=*/{}, /*successes=*/{}); |
| jumbf::SuperBox root_box; |
| std::vector<jumbf::SuperBox> manifests; |
| manifests.push_back(CreateStandardManifest("urn:c2pa:1", {})); |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:1", |
| {empty_data_hash_assertion_, |
| ParseTextProtoOrDie<Assertion>( |
| R"pb(ingredient_v3 { |
| relationship: "inputTo" |
| claim_signature { |
| url: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.signature" |
| } |
| })pb")}); |
| jumbf::SuperBox manifest_store = CreateManifestStore(manifests); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| ManifestGraph graph(&manifests[0], &uri_resolver, &assertion_validator, |
| &claim_validator, &noop_hash_checker_factory_, &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| EXPECT_THAT(**result, EqualsProto(ParseTextProtoOrDie< |
| PartialValidationResultProto>(R"pb( |
| active_manifest { |
| label: "urn:c2pa:1" |
| claim { default_algorithm: "sha256" } |
| assertions { |
| label: "c2pa.hash.data" |
| data_hash {} |
| } |
| assertions { |
| ingredient_v3 { |
| relationship: "inputTo" |
| claim_signature { url: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.signature" } |
| } |
| } |
| validation { |
| # No ingredient.unknownProvenance informational for inputTo ingredient. |
| successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:1" |
| } |
| } |
| } |
| hard_binding_uri: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.assertions/c2pa.hash.data" |
| )pb"))); |
| } |
| |
| TEST_F(ManifestGraphTest, IngredientV3WithValidClaimSignatureUri) { |
| FakeClaimValidator claim_validator( |
| /*failures=*/{}, /*informationals=*/{}, |
| /*successes=*/{SuccessStatusCode::kClaimSignatureValidated}); |
| claim_validator.SetClaimResult("urn:c2pa:1", DefaultClaim()); |
| claim_validator.SetClaimResult("urn:c2pa:2", DefaultClaim()); |
| FakeAssertionValidator assertion_validator(/*failures=*/{}, /*successes=*/{}); |
| jumbf::SuperBox root_box; |
| std::vector<jumbf::SuperBox> manifests; |
| manifests.push_back( |
| CreateStandardManifest("urn:c2pa:1", {claim_signature_box_})); |
| manifests.push_back(CreateStandardManifest("urn:c2pa:2", {})); |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:1", |
| {empty_data_hash_assertion_, |
| ParseTextProtoOrDie<Assertion>( |
| R"pb( |
| label: "assert" |
| ingredient_v3 { |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:2" } |
| claim_signature { |
| url: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.signature" |
| } |
| })pb")}); |
| jumbf::SuperBox manifest_store = CreateManifestStore(manifests); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| ManifestGraph graph(&manifests[0], &uri_resolver, &assertion_validator, |
| &claim_validator, &noop_hash_checker_factory_, &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| EXPECT_THAT(**result, EqualsProto(ParseTextProtoOrDie< |
| PartialValidationResultProto>(R"pb( |
| active_manifest { |
| label: "urn:c2pa:1" |
| claim { default_algorithm: "sha256" } |
| assertions { |
| label: "c2pa.hash.data" |
| data_hash {} |
| } |
| assertions { |
| label: "assert" |
| ingredient_v3 { |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:2" } |
| claim_signature { url: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.signature" } |
| } |
| } |
| validation { |
| successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:1" |
| } |
| successes { |
| code: "ingredient.claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.assertions/assert" |
| } |
| } |
| } |
| ingredient_manifests { |
| label: "urn:c2pa:2" |
| claim { default_algorithm: "sha256" } |
| validation { |
| successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:2" |
| } |
| } |
| } |
| hard_binding_uri: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.assertions/c2pa.hash.data" |
| )pb"))); |
| } |
| |
| TEST_F(ManifestGraphTest, IngredientV3WithMultipleParents) { |
| FakeClaimValidator claim_validator( |
| /*failures=*/{}, /*informationals=*/{}, |
| /*successes=*/{SuccessStatusCode::kClaimSignatureValidated}); |
| claim_validator.SetClaimResult("urn:c2pa:1", DefaultClaim()); |
| claim_validator.SetClaimResult("urn:c2pa:2", DefaultClaim()); |
| claim_validator.SetClaimResult("urn:c2pa:3", DefaultClaim()); |
| claim_validator.SetClaimResult("urn:c2pa:4", DefaultClaim()); |
| FakeAssertionValidator assertion_validator(/*failures=*/{}, /*successes=*/{}); |
| jumbf::SuperBox root_box; |
| std::vector<jumbf::SuperBox> manifests; |
| manifests.push_back(CreateStandardManifest("urn:c2pa:1", {})); |
| manifests.push_back(CreateStandardManifest("urn:c2pa:2", {})); |
| manifests.push_back(CreateStandardManifest("urn:c2pa:3", {})); |
| manifests.push_back(CreateStandardManifest("urn:c2pa:4", {})); |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:1", |
| {empty_data_hash_assertion_, |
| ParseTextProtoOrDie<Assertion>( |
| R"pb( |
| label: "assert_for_2" |
| ingredient_v3 { |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:2" } |
| })pb"), |
| ParseTextProtoOrDie<Assertion>( |
| R"pb( |
| label: "assert_for_3" |
| ingredient_v3 { |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:3" } |
| })pb")}); |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:2", |
| {ParseTextProtoOrDie<Assertion>( |
| R"pb( |
| label: "assert_for_4" |
| ingredient_v3 { |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:4" } |
| })pb")}); |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:3", |
| {ParseTextProtoOrDie<Assertion>( |
| R"pb( |
| label: "assert_for_4" |
| ingredient_v3 { |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:4" } |
| })pb")}); |
| jumbf::SuperBox manifest_store = CreateManifestStore(manifests); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| ManifestGraph graph(&manifests[0], &uri_resolver, &assertion_validator, |
| &claim_validator, &noop_hash_checker_factory_, &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| EXPECT_THAT(**result, EqualsProto(ParseTextProtoOrDie< |
| PartialValidationResultProto>(R"pb( |
| active_manifest { |
| label: "urn:c2pa:1" |
| validation { |
| successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:1" |
| } |
| successes { |
| code: "ingredient.manifest.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.assertions/assert_for_2" |
| } |
| successes { |
| code: "ingredient.manifest.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.assertions/assert_for_3" |
| } |
| } |
| claim { default_algorithm: "sha256" } |
| assertions { |
| label: "c2pa.hash.data" |
| data_hash {} |
| } |
| assertions { |
| label: "assert_for_2" |
| ingredient_v3 { active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:2" } } |
| } |
| assertions { |
| label: "assert_for_3" |
| ingredient_v3 { active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:3" } } |
| } |
| } |
| ingredient_manifests { |
| label: "urn:c2pa:2" |
| validation { |
| successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:2" |
| } |
| successes { |
| code: "ingredient.manifest.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:2/c2pa.assertions/assert_for_4" |
| } |
| } |
| claim { default_algorithm: "sha256" } |
| assertions { |
| label: "assert_for_4" |
| ingredient_v3 { active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:4" } } |
| } |
| } |
| ingredient_manifests { |
| label: "urn:c2pa:4" |
| validation { |
| successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:4" |
| } |
| } |
| claim { default_algorithm: "sha256" } |
| } |
| ingredient_manifests { |
| label: "urn:c2pa:3" |
| validation { |
| successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:3" |
| } |
| successes { |
| code: "ingredient.manifest.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:3/c2pa.assertions/assert_for_4" |
| } |
| } |
| claim { default_algorithm: "sha256" } |
| assertions { |
| label: "assert_for_4" |
| ingredient_v3 { active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:4" } } |
| } |
| } |
| hard_binding_uri: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.assertions/c2pa.hash.data" |
| )pb"))); |
| } |
| |
| TEST_F(ManifestGraphTest, IngredientV3AllButOneIngredientManifestIsValid) { |
| FakeClaimValidator claim_validator(/*failures=*/{}, /*informationals=*/{}, |
| /*successes=*/{}); |
| claim_validator.SetClaimResult("urn:c2pa:1", DefaultClaim()); |
| claim_validator.SetClaimSuccesses( |
| "urn:c2pa:1", {SuccessStatusCode::kClaimSignatureValidated}); |
| claim_validator.SetClaimResult("urn:c2pa:2", DefaultClaim()); |
| claim_validator.SetClaimSuccesses( |
| "urn:c2pa:2", {SuccessStatusCode::kClaimSignatureValidated}); |
| claim_validator.SetClaimResult("urn:c2pa:3", DefaultClaim()); |
| claim_validator.SetClaimSuccesses( |
| "urn:c2pa:3", {SuccessStatusCode::kClaimSignatureValidated}); |
| claim_validator.SetClaimInformationals( |
| "urn:c2pa:3", {InformationalStatusCode::kIngredientUnknownProvenance}); |
| claim_validator.SetClaimResult("urn:c2pa:4", DefaultClaim()); |
| claim_validator.SetClaimFailures( |
| "urn:c2pa:4", {FailureStatusCode::kClaimSignatureMismatch}); |
| FakeAssertionValidator assertion_validator(/*failures=*/{}, /*successes=*/{}); |
| jumbf::SuperBox root_box; |
| std::vector<jumbf::SuperBox> manifests; |
| manifests.push_back(CreateStandardManifest("urn:c2pa:1", {})); |
| manifests.push_back(CreateStandardManifest("urn:c2pa:2", {})); |
| manifests.push_back(CreateStandardManifest("urn:c2pa:3", {})); |
| manifests.push_back(CreateStandardManifest("urn:c2pa:4", {})); |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:1", |
| {empty_data_hash_assertion_, |
| ParseTextProtoOrDie<Assertion>( |
| R"pb( |
| label: "assert_for_2" |
| ingredient_v3 { |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:2" } |
| })pb"), |
| ParseTextProtoOrDie<Assertion>( |
| R"pb( |
| label: "assert_for_3" |
| ingredient_v3 { |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:3" } |
| })pb"), |
| ParseTextProtoOrDie<Assertion>( |
| R"pb( |
| label: "assert_for_4" |
| ingredient_v3 { |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:4" } |
| })pb")}); |
| jumbf::SuperBox manifest_store = CreateManifestStore(manifests); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| ManifestGraph graph(&manifests[0], &uri_resolver, &assertion_validator, |
| &claim_validator, &noop_hash_checker_factory_, &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| EXPECT_THAT(**result, EqualsProto(ParseTextProtoOrDie< |
| PartialValidationResultProto>(R"pb( |
| active_manifest { |
| label: "urn:c2pa:1" |
| claim { default_algorithm: "sha256" } |
| assertions { |
| label: "c2pa.hash.data" |
| data_hash {} |
| } |
| assertions { |
| label: "assert_for_2" |
| ingredient_v3 { active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:2" } } |
| } |
| assertions { |
| label: "assert_for_3" |
| ingredient_v3 { active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:3" } } |
| } |
| assertions { |
| label: "assert_for_4" |
| ingredient_v3 { active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:4" } } |
| } |
| validation { |
| successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:1" |
| } |
| successes { |
| code: "ingredient.manifest.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.assertions/assert_for_2" |
| } |
| successes { |
| code: "ingredient.manifest.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.assertions/assert_for_3" |
| } |
| successes { |
| code: "ingredient.manifest.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.assertions/assert_for_4" |
| } |
| } |
| } |
| ingredient_manifests { |
| label: "urn:c2pa:2" |
| claim { default_algorithm: "sha256" } |
| validation { |
| successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:2" |
| } |
| } |
| } |
| ingredient_manifests { |
| label: "urn:c2pa:3" |
| claim { default_algorithm: "sha256" } |
| validation { |
| successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:3" |
| } |
| informationals { |
| code: "ingredient.unknownProvenance" |
| url: "self#jumbf=/c2pa/urn:c2pa:3" |
| } |
| } |
| } |
| ingredient_manifests { |
| label: "urn:c2pa:4" |
| claim { default_algorithm: "sha256" } |
| validation { |
| failures { |
| code: "claimSignature.mismatch" |
| url: "self#jumbf=/c2pa/urn:c2pa:4" |
| } |
| } |
| } |
| hard_binding_uri: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.assertions/c2pa.hash.data" |
| )pb"))); |
| } |
| |
| TEST_F(ManifestGraphTest, IngredientV3WithRedactions) { |
| FakeClaimValidator claim_validator( |
| /*failures=*/{}, /*informationals=*/{}, |
| /*successes=*/{SuccessStatusCode::kClaimSignatureValidated}); |
| Claim claim1 = DefaultClaim(); |
| Claim claim2 = DefaultClaim(); |
| claim1.add_redacted_assertions("self#jumbf=/c2pa/urn:c2pa:2/some_assertion"); |
| claim2.add_created_assertions()->set_url("self#jumbf=some_assertion"); |
| claim_validator.SetClaimResult("urn:c2pa:1", claim1); |
| claim_validator.SetClaimResult("urn:c2pa:2", claim2); |
| claim_validator.SetClaimResult("urn:c2pa:3", DefaultClaim()); |
| claim_validator.SetClaimResult("urn:c2pa:4", DefaultClaim()); |
| FakeAssertionValidator assertion_validator(/*failures=*/{}, /*successes=*/{}); |
| jumbf::SuperBox root_box; |
| std::vector<jumbf::SuperBox> manifests; |
| manifests.push_back(CreateStandardManifest("urn:c2pa:1", {})); |
| manifests.push_back(CreateStandardManifest("urn:c2pa:2", {})); |
| manifests.push_back(CreateStandardManifest("urn:c2pa:3", {})); |
| manifests.push_back(CreateStandardManifest("urn:c2pa:4", {})); |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:1", |
| {empty_data_hash_assertion_, |
| ParseTextProtoOrDie<Assertion>( |
| R"pb( |
| label: "assert_for_2" |
| ingredient_v3 { |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:2" } |
| })pb"), |
| ParseTextProtoOrDie<Assertion>( |
| R"pb( |
| label: "assert_for_3" |
| ingredient_v3 { |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:3" } |
| })pb")}); |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:2", |
| {ParseTextProtoOrDie<Assertion>( |
| R"pb( |
| label: "assert_for_4" |
| ingredient_v3 { |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:4" } |
| })pb")}); |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:3", |
| {ParseTextProtoOrDie<Assertion>( |
| R"pb( |
| label: "assert_for_4" |
| ingredient_v3 { |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:4" } |
| })pb")}); |
| jumbf::SuperBox manifest_store = CreateManifestStore(manifests); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| ManifestGraph graph(&manifests[0], &uri_resolver, &assertion_validator, |
| &claim_validator, &noop_hash_checker_factory_, &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| EXPECT_THAT(**result, EqualsProto(ParseTextProtoOrDie< |
| PartialValidationResultProto>(R"pb( |
| active_manifest { |
| label: "urn:c2pa:1" |
| validation { |
| successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:1" |
| } |
| successes { |
| code: "ingredient.manifest.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.assertions/assert_for_2" |
| } |
| successes { |
| code: "ingredient.manifest.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.assertions/assert_for_3" |
| } |
| } |
| claim { |
| redacted_assertions: "self#jumbf=/c2pa/urn:c2pa:2/some_assertion" |
| default_algorithm: "sha256" |
| } |
| assertions { |
| label: "c2pa.hash.data" |
| data_hash {} |
| } |
| assertions { |
| label: "assert_for_2" |
| ingredient_v3 { active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:2" } } |
| } |
| assertions { |
| label: "assert_for_3" |
| ingredient_v3 { active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:3" } } |
| } |
| } |
| ingredient_manifests { |
| label: "urn:c2pa:2" |
| validation { |
| successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:2" |
| } |
| successes { |
| code: "ingredient.manifest.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:2/c2pa.assertions/assert_for_4" |
| } |
| } |
| claim { |
| created_assertions { url: "self#jumbf=some_assertion" } |
| default_algorithm: "sha256" |
| } |
| assertions { |
| label: "assert_for_4" |
| ingredient_v3 { active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:4" } } |
| } |
| } |
| ingredient_manifests { |
| label: "urn:c2pa:4" |
| validation { |
| successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:4" |
| } |
| } |
| claim { default_algorithm: "sha256" } |
| } |
| ingredient_manifests { |
| label: "urn:c2pa:3" |
| validation { |
| successes { |
| code: "claimSignature.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:3" |
| } |
| successes { |
| code: "ingredient.manifest.validated" |
| url: "self#jumbf=/c2pa/urn:c2pa:3/c2pa.assertions/assert_for_4" |
| } |
| } |
| claim { default_algorithm: "sha256" } |
| assertions { |
| label: "assert_for_4" |
| ingredient_v3 { active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:4" } } |
| } |
| } |
| hard_binding_uri: "self#jumbf=/c2pa/urn:c2pa:1/c2pa.assertions/c2pa.hash.data" |
| )pb"))); |
| } |
| |
| TEST_F(ManifestGraphTest, IngredientAssertionsWithFailures) { |
| FakeClaimValidator claim_validator( |
| /*failures=*/{}, /*informationals=*/{}, |
| /*successes=*/{SuccessStatusCode::kClaimSignatureValidated}); |
| claim_validator.SetClaimResult("urn:c2pa:1", DefaultClaim()); |
| claim_validator.SetClaimResult("urn:c2pa:2", DefaultClaim()); |
| claim_validator.SetClaimResult("urn:c2pa:3", DefaultClaim()); |
| claim_validator.SetClaimResult("urn:c2pa:4", DefaultClaim()); |
| FakeAssertionValidator assertion_validator(/*failures=*/{}, /*successes=*/{}); |
| jumbf::SuperBox root_box; |
| std::vector<jumbf::SuperBox> manifests; |
| manifests.push_back(CreateStandardManifest("urn:c2pa:1", {})); |
| manifests.push_back(CreateStandardManifest("urn:c2pa:2", {})); |
| manifests.push_back(CreateStandardManifest("urn:c2pa:3", {})); |
| manifests.push_back(CreateStandardManifest("urn:c2pa:4", {})); |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:1", |
| {empty_data_hash_assertion_, |
| ParseTextProtoOrDie<Assertion>( |
| R"pb( |
| label: "assert_for_2" |
| ingredient_v3 { |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:2" } |
| validation_results { |
| active_manifest { |
| failures { code: "assertion.dataHash.mismatch" url: "blah2" } |
| } |
| } |
| })pb"), |
| ParseTextProtoOrDie<Assertion>( |
| R"pb( |
| label: "assert_for_3" |
| ingredient_v3 { |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:3" } |
| })pb")}); |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:2", |
| {ParseTextProtoOrDie<Assertion>( |
| R"pb( |
| label: "assert_for_4" |
| ingredient_v3 { |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:4" } |
| validation_results { |
| active_manifest { |
| failures { code: "assertion.dataHash.mismatch" url: "blah4" } |
| } |
| } |
| })pb")}); |
| assertion_validator.SetOutputAssertions( |
| "/c2pa/urn:c2pa:3", |
| {ParseTextProtoOrDie<Assertion>( |
| R"pb( |
| label: "assert_for_4" |
| ingredient_v3 { |
| active_manifest { url: "self#jumbf=/c2pa/urn:c2pa:4" } |
| validation_results { |
| active_manifest { |
| failures { code: "assertion.dataHash.malformed" url: "blah4" } |
| } |
| } |
| })pb")}); |
| jumbf::SuperBox manifest_store = CreateManifestStore(manifests); |
| jumbf::UriResolver uri_resolver = |
| jumbf::UriResolver::WithSingleRootChild(&manifest_store); |
| ManifestGraph graph(&manifests[0], &uri_resolver, &assertion_validator, |
| &claim_validator, &noop_hash_checker_factory_, &options_); |
| auto result = graph.Validate(); |
| ABSL_ASSERT_OK(result); |
| ASSERT_THAT((*result)->ingredient_manifests(), SizeIs(3)); |
| EXPECT_THAT((*result)->ingredient_manifests(0).label(), "urn:c2pa:2"); |
| EXPECT_THAT((*result)->ingredient_manifests(0).validation().failures(), |
| Contains(EqualsProto(ParseTextProtoOrDie<ValidationStatus>(R"pb( |
| code: "assertion.dataHash.mismatch" |
| url: "blah2" |
| explanation: "from original validator" |
| )pb")))); |
| EXPECT_THAT((*result)->ingredient_manifests(1).label(), "urn:c2pa:4"); |
| EXPECT_THAT((*result)->ingredient_manifests(1).validation().failures(), |
| Contains(EqualsProto(ParseTextProtoOrDie<ValidationStatus>(R"pb( |
| code: "assertion.dataHash.mismatch" |
| url: "blah4" |
| explanation: "from original validator" |
| )pb")))); |
| } |
| |
| } // namespace |
| } // namespace credentio |