blob: 2316c927cc977b65d793403f48f7480d063b3507 [file]
// 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