| // 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 "claim/generator.h" | |
| #include <memory> | |
| #include <string> | |
| #include <utility> | |
| #include "absl/log/absl_check.h" | |
| #include "absl/status/status.h" | |
| #include "absl/status/status_matchers.h" | |
| #include "absl/status/statusor.h" | |
| #include "absl/strings/cord.h" | |
| #include "absl/strings/string_view.h" | |
| #include "absl/synchronization/notification.h" | |
| #include "assertion/assertion_encoder.h" | |
| #include "async/async_context.h" | |
| #include "claim/claim_cbor_encoder.h" | |
| #include "claim/local_signer.h" | |
| #include "claim/manifest_box.h" | |
| #include "claim/manifest_box_assembler.h" | |
| #include "claim/manifest_box_encoder.h" | |
| #include "claim/manifest_store_builder.h" | |
| #include "claim/signer.h" | |
| #include "crypto/crypto_read_handler.h" | |
| #include "crypto/crypto_write_handler.h" | |
| #include "crypto/default/default_crypto_read_handler.h" | |
| #include "crypto/default/hasher.h" | |
| #include "gmock/gmock.h" | |
| #include "google/protobuf/util/message_differencer.h" | |
| #include "gtest/gtest.h" | |
| #include "jumbf/box_builder.h" | |
| #include "proto/assertion.pb.h" | |
| #include "proto/generator.pb.h" | |
| #include "proto/generator_info.pb.h" | |
| #include "proto/manifest.pb.h" | |
| #include "proto/signature_info.pb.h" | |
| #include "proto/validation_result.pb.h" | |
| #include "testing/create_test_crypto_write_handler.h" | |
| #include "testing/manifest_store_test_utils.h" | |
| #include "testing/proto_test_utils.h" | |
| #include "uuid/uuid.h" | |
| namespace credentio { | |
| namespace { | |
| using ::absl_testing::IsOkAndHolds; | |
| using ::absl_testing::StatusIs; | |
| using ::credentio_testing::EqualsProto; | |
| using ::credentio_testing::ParseTextProtoOrDie; | |
| using ::credentio_testing::PartiallyEqualsProto; | |
| using ::testing::HasSubstr; | |
| using ::testing::Pointee; | |
| using ::testing::Pointwise; | |
| MATCHER_P(EqualsProtoIgnoringTimestampAndOrdering, expected, "") { | |
| google::protobuf::util::MessageDifferencer differencer; | |
| differencer.set_repeated_field_comparison( | |
| google::protobuf::util::MessageDifferencer::AS_SET); | |
| differencer.IgnoreField( | |
| SignatureInfo::descriptor()->FindFieldByName("timestamping_time")); | |
| std::string diff; | |
| differencer.ReportDifferencesToString(&diff); | |
| bool match = differencer.Compare(expected, arg); | |
| if (!match) { | |
| *result_listener << "\nDifference:\n" << diff; | |
| } | |
| return match; | |
| } | |
| constexpr Uuid kUuid = | |
| Uuid::FromStringOrDie("03020100-0504-0706-0908-0f0e0d0c0b0a"); | |
| class FakeUuidGenerator : public UuidGenerator { | |
| public: | |
| Uuid Generate() const override { return kUuid; } | |
| }; | |
| class ManifestAssemblerTest : public testing::Test { | |
| protected: | |
| ManifestAssemblerTest() { | |
| absl::StatusOr<std::unique_ptr<ManifestBoxAssembler>> manifest_generator = | |
| CreateManifestAssembler({.uuid_generator = &uuid_generator_, | |
| .hash_factory = &hash_factory_}); | |
| ABSL_CHECK_OK(manifest_generator); | |
| manifest_assembler_ = std::move(*manifest_generator); | |
| } | |
| FakeUuidGenerator uuid_generator_; | |
| Sha256HasherFactory hash_factory_; | |
| std::unique_ptr<ManifestBoxAssembler> manifest_assembler_; | |
| }; | |
| TEST_F(ManifestAssemblerTest, NoHardBindingInvalid) { | |
| GenerateManifestStoreRequest request; | |
| EXPECT_THAT( | |
| manifest_assembler_->Assemble(request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("hard binding"))); | |
| } | |
| TEST_F(ManifestAssemblerTest, PopulateBmffBasedHardBinding) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { | |
| bmff_based_hash { | |
| exclusions { xpath: "" } | |
| alg: "some alg" | |
| hash: "some hash" | |
| } | |
| } | |
| actions { created {} } | |
| )pb"); | |
| ManifestBox expected = { | |
| .label = "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a", | |
| .hard_binding_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.hash.bmff.v3" | |
| bmff_based_hash { | |
| exclusions { xpath: "" } | |
| alg: "some alg" | |
| hash: "some hash" | |
| } | |
| )pb"), | |
| .actions_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { actions { action: "c2pa.created" } } | |
| )pb"), | |
| .ingredient_assertions = {}, | |
| .manifest_store_builder = ManifestStoreBuilder(&hash_factory_), | |
| }; | |
| absl::StatusOr<ManifestBox> manifest_box = | |
| manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box); | |
| EXPECT_EQ(manifest_box->label, expected.label); | |
| EXPECT_THAT(manifest_box->hard_binding_assertion, | |
| EqualsProto(expected.hard_binding_assertion)); | |
| EXPECT_THAT(manifest_box->actions_assertion, | |
| EqualsProto(expected.actions_assertion)); | |
| EXPECT_THAT(manifest_box->ingredient_assertions, | |
| Pointwise(EqualsProto(), expected.ingredient_assertions)); | |
| } | |
| TEST_F(ManifestAssemblerTest, PopulateActionsAssertionWithAllActionsIncluded) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { | |
| bmff_based_hash { | |
| exclusions { xpath: "" } | |
| alg: "some alg" | |
| hash: "some hash" | |
| } | |
| } | |
| actions { | |
| created {} | |
| all_actions_included: true | |
| } | |
| )pb"); | |
| ManifestBox expected = { | |
| .label = "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a", | |
| .hard_binding_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.hash.bmff.v3" | |
| bmff_based_hash { | |
| exclusions { xpath: "" } | |
| alg: "some alg" | |
| hash: "some hash" | |
| } | |
| )pb"), | |
| .actions_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { | |
| all_actions_included: true | |
| actions { action: "c2pa.created" } | |
| } | |
| )pb"), | |
| .ingredient_assertions = {}, | |
| .manifest_store_builder = ManifestStoreBuilder(&hash_factory_), | |
| }; | |
| absl::StatusOr<ManifestBox> manifest_box = | |
| manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box); | |
| EXPECT_EQ(manifest_box->label, expected.label); | |
| EXPECT_THAT(manifest_box->hard_binding_assertion, | |
| EqualsProto(expected.hard_binding_assertion)); | |
| EXPECT_THAT(manifest_box->actions_assertion, | |
| EqualsProto(expected.actions_assertion)); | |
| EXPECT_THAT(manifest_box->ingredient_assertions, | |
| Pointwise(EqualsProto(), expected.ingredient_assertions)); | |
| } | |
| TEST_F(ManifestAssemblerTest, PopulateBoxesBasedHardBinding) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { | |
| box_hash { | |
| alg: "some alg" | |
| boxes { names: "some name" names: "other name" hash: "some hash" } | |
| } | |
| } | |
| actions { created {} } | |
| )pb"); | |
| ManifestBox expected = { | |
| .label = "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a", | |
| .hard_binding_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.hash.boxes" | |
| boxes_hash { | |
| alg: "some alg" | |
| boxes { | |
| names: "some name" | |
| names: "other name" | |
| hash: "some hash" | |
| pad: "" | |
| pad2: "" | |
| } | |
| } | |
| )pb"), | |
| .actions_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { actions { action: "c2pa.created" } } | |
| )pb"), | |
| .ingredient_assertions = {}, | |
| .manifest_store_builder = ManifestStoreBuilder(&hash_factory_), | |
| }; | |
| absl::StatusOr<ManifestBox> manifest_box = | |
| manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box); | |
| EXPECT_EQ(manifest_box->label, expected.label); | |
| EXPECT_THAT(manifest_box->hard_binding_assertion, | |
| EqualsProto(expected.hard_binding_assertion)); | |
| EXPECT_THAT(manifest_box->actions_assertion, | |
| EqualsProto(expected.actions_assertion)); | |
| EXPECT_THAT(manifest_box->ingredient_assertions, | |
| Pointwise(EqualsProto(), expected.ingredient_assertions)); | |
| } | |
| TEST_F(ManifestAssemblerTest, PopulateDataBasedHardBinding) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { | |
| data_hash { | |
| alg: "some alg" | |
| hash: "some hash" | |
| exclusions { start: 1 length: 10 } | |
| } | |
| } | |
| actions { created {} } | |
| )pb"); | |
| ManifestBox expected = { | |
| .label = "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a", | |
| .hard_binding_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.hash.data" | |
| data_hash { | |
| alg: "some alg" | |
| hash: "some hash" | |
| exclusions { start: 1 length: 10 } | |
| pad: "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000" | |
| } | |
| )pb"), | |
| .actions_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { actions { action: "c2pa.created" } } | |
| )pb"), | |
| .ingredient_assertions = {}, | |
| .manifest_store_builder = ManifestStoreBuilder(&hash_factory_), | |
| }; | |
| absl::StatusOr<ManifestBox> manifest_box = | |
| manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box); | |
| EXPECT_EQ(manifest_box->label, expected.label); | |
| EXPECT_THAT(manifest_box->hard_binding_assertion, | |
| EqualsProto(expected.hard_binding_assertion)); | |
| EXPECT_THAT(manifest_box->actions_assertion, | |
| EqualsProto(expected.actions_assertion)); | |
| EXPECT_THAT(manifest_box->ingredient_assertions, | |
| Pointwise(EqualsProto(), expected.ingredient_assertions)); | |
| } | |
| TEST_F(ManifestAssemblerTest, UnspecifiedInceptionActionInvalid) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash { exclusions { xpath: "" } } } | |
| )pb"); | |
| EXPECT_THAT(manifest_assembler_->Assemble(request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("missing inception action"))); | |
| } | |
| TEST_F(ManifestAssemblerTest, PlacedAction) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash { exclusions { xpath: "" } } } | |
| actions { | |
| created {} | |
| actions { placed { ingredient_indexes: 0 } } | |
| } | |
| ingredients { metadata { title: "ingredient0" } } | |
| )pb"); | |
| absl::StatusOr<ManifestBox> manifest_box = | |
| manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box); | |
| EXPECT_THAT(manifest_box->actions_assertion, | |
| PartiallyEqualsProto(ParseTextProtoOrDie<Assertion>(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { action: "c2pa.created" } | |
| actions { action: "c2pa.placed" } | |
| } | |
| )pb"))); | |
| } | |
| TEST_F(ManifestAssemblerTest, HasCreatedAction) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash { exclusions { xpath: "" } } } | |
| actions { | |
| created { | |
| action_metadata { | |
| software_agent {} | |
| description: "created for testing" | |
| } | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| } | |
| )pb"); | |
| ManifestBox expected = { | |
| .label = "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a", | |
| .hard_binding_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.hash.bmff.v3" | |
| bmff_based_hash { exclusions { xpath: "" } } | |
| )pb"), | |
| .actions_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { | |
| action: "c2pa.created" | |
| software_agent {} | |
| description: "created for testing" | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| } | |
| )pb"), | |
| .ingredient_assertions = {}, | |
| .manifest_store_builder = ManifestStoreBuilder(&hash_factory_), | |
| }; | |
| absl::StatusOr<ManifestBox> manifest_box = | |
| manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box); | |
| EXPECT_EQ(manifest_box->label, expected.label); | |
| EXPECT_THAT(manifest_box->hard_binding_assertion, | |
| EqualsProto(expected.hard_binding_assertion)); | |
| EXPECT_THAT(manifest_box->actions_assertion, | |
| EqualsProto(expected.actions_assertion)); | |
| EXPECT_THAT(manifest_box->ingredient_assertions, | |
| Pointwise(EqualsProto(), expected.ingredient_assertions)); | |
| } | |
| TEST_F(ManifestAssemblerTest, OpenedActionWithoutIngredientIndex) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash { exclusions { xpath: "" } } } | |
| actions { | |
| opened { | |
| action_metadata { | |
| software_agent {} | |
| description: "opened for testing" | |
| } | |
| } | |
| } | |
| )pb"); | |
| EXPECT_THAT(manifest_assembler_->Assemble(request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("requires an ingredient index."))); | |
| } | |
| TEST_F(ManifestAssemblerTest, OpenedActionWithInvalidIngredientIndex) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash { exclusions { xpath: "" } } } | |
| actions { | |
| opened { | |
| action_metadata { | |
| software_agent {} | |
| description: "opened for testing" | |
| } | |
| ingredient_index: 1 | |
| } | |
| } | |
| ingredients { metadata { title: "test ingredient" } } | |
| )pb"); | |
| EXPECT_THAT(manifest_assembler_->Assemble(request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("ingredient index out of range"))); | |
| } | |
| TEST_F(ManifestAssemblerTest, OpenedActionWithUnreferencedIngredient) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash { exclusions { xpath: "" } } } | |
| actions { | |
| opened { | |
| action_metadata { | |
| software_agent {} | |
| description: "opened for testing" | |
| } | |
| ingredient_index: 1 | |
| } | |
| } | |
| ingredients { metadata { title: "test ingredient" } } | |
| ingredients { metadata { title: "test ingredient at index 1" } } | |
| )pb"); | |
| EXPECT_THAT( | |
| manifest_assembler_->Assemble(request), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("ingredient is not referenced by any action"))); | |
| } | |
| TEST_F(ManifestAssemblerTest, HasSimpleOpenedAction) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash { exclusions { xpath: "" } } } | |
| actions { | |
| opened { | |
| action_metadata { | |
| software_agent {} | |
| description: "opened for testing" | |
| } | |
| ingredient_index: 0 | |
| } | |
| } | |
| ingredients { metadata { title: "test ingredient" } } | |
| )pb"); | |
| ManifestBox expected = { | |
| .label = "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a", | |
| .hard_binding_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.hash.bmff.v3" | |
| bmff_based_hash { exclusions { xpath: "" } } | |
| )pb"), | |
| .actions_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { | |
| action: "c2pa.opened" | |
| software_agent {} | |
| description: "opened for testing" | |
| parameters { | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" | |
| } | |
| } | |
| } | |
| } | |
| )pb"), | |
| .ingredient_assertions = {ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.ingredient.v3" | |
| ingredient_v3 { title: "test ingredient" relationship: "parentOf" } | |
| )pb")}, | |
| .manifest_store_builder = ManifestStoreBuilder(&hash_factory_), | |
| }; | |
| absl::StatusOr<ManifestBox> manifest_box = | |
| manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box); | |
| EXPECT_EQ(manifest_box->label, expected.label); | |
| EXPECT_THAT(manifest_box->hard_binding_assertion, | |
| EqualsProto(expected.hard_binding_assertion)); | |
| EXPECT_THAT(manifest_box->actions_assertion, | |
| EqualsProto(expected.actions_assertion)); | |
| EXPECT_THAT(manifest_box->ingredient_assertions, | |
| Pointwise(EqualsProto(), expected.ingredient_assertions)); | |
| } | |
| TEST_F(ManifestAssemblerTest, HasOpenedAction) { | |
| GenerateManifestStoreRequest request = ParseTextProtoOrDie(R"pb( | |
| hard_binding { bmff_based_hash { exclusions { xpath: "" } } } | |
| actions { | |
| opened { | |
| action_metadata { | |
| software_agent {} | |
| description: "opened for testing" | |
| } | |
| ingredient_index: 0 | |
| } | |
| } | |
| ingredients { | |
| metadata { | |
| title: "test ingredient" | |
| media_type: "video/mp4" | |
| instance_id: "test_ingredient" | |
| description: "test ingredient" | |
| informational_uri: "https://test.com/ingredient" | |
| } | |
| } | |
| )pb"); | |
| ManifestBox expected = { | |
| .label = "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a", | |
| .hard_binding_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.hash.bmff.v3" | |
| bmff_based_hash { exclusions { xpath: "" } } | |
| )pb"), | |
| .actions_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { | |
| action: "c2pa.opened" | |
| software_agent {} | |
| description: "opened for testing" | |
| parameters { | |
| ingredients { | |
| url: "self#jumbf=c2pa.assertions/c2pa.ingredient.v3" | |
| } | |
| } | |
| } | |
| } | |
| )pb"), | |
| .ingredient_assertions = {ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.ingredient.v3" | |
| ingredient_v3 { | |
| title: "test ingredient" | |
| media_type: "video/mp4" | |
| relationship: "parentOf" | |
| instance_id: "test_ingredient" | |
| description: "test ingredient" | |
| informational_uri: "https://test.com/ingredient" | |
| } | |
| )pb")}, | |
| .manifest_store_builder = ManifestStoreBuilder(&hash_factory_), | |
| }; | |
| absl::StatusOr<ManifestBox> manifest_box = | |
| manifest_assembler_->Assemble(request); | |
| ABSL_ASSERT_OK(manifest_box); | |
| EXPECT_EQ(manifest_box->label, expected.label); | |
| EXPECT_THAT(manifest_box->hard_binding_assertion, | |
| EqualsProto(expected.hard_binding_assertion)); | |
| EXPECT_THAT(manifest_box->actions_assertion, | |
| EqualsProto(expected.actions_assertion)); | |
| EXPECT_THAT(manifest_box->ingredient_assertions, | |
| Pointwise(EqualsProto(), expected.ingredient_assertions)); | |
| } | |
| absl::StatusOr<jumbf::BuiltSuperBox> Encode(ManifestEncoder& manifest_encoder, | |
| ManifestBox& manifest_box) { | |
| absl::Notification done; | |
| absl::StatusOr<ManifestEncoderResult> result; | |
| GenerateManifestStoreOptions options; | |
| manifest_encoder.Encode( | |
| &manifest_box, options, AsyncContext(), | |
| [&result, &done](absl::StatusOr<ManifestEncoderResult> res) { | |
| result = std::move(res); | |
| done.Notify(); | |
| }); | |
| done.WaitForNotification(); | |
| if (!result.ok()) { | |
| return result.status(); | |
| } | |
| return std::move(result->box); | |
| } | |
| class ManifestEncoderTest : public testing::Test { | |
| protected: | |
| ManifestEncoderTest() { | |
| generator_info_.set_name("Test generator"); | |
| absl::StatusOr<std::unique_ptr<CryptoWriteHandler>> crypto_writer = | |
| CreateTestCryptoWriteHandler({}); | |
| ABSL_CHECK_OK(crypto_writer); | |
| crypto_writer_ = std::move(*crypto_writer); | |
| absl::StatusOr<std::unique_ptr<CryptoReadHandler>> crypto_reader = | |
| CreateDefaultCryptoReadHandler({ | |
| .trust_environment = CryptoReadHandler::TrustEnvironment::kAny, | |
| }); | |
| ABSL_CHECK_OK(crypto_reader); | |
| crypto_reader_ = std::move(*crypto_reader); | |
| claim_signer_ = std::make_unique<LocalClaimSigner>(crypto_writer_.get(), | |
| &hash_factory_); | |
| absl::StatusOr<std::unique_ptr<ManifestEncoder>> manifest_encoder = | |
| CreateManifestEncoder({ | |
| .generator_info = generator_info_, | |
| .claim_signer = claim_signer_.get(), | |
| .assertion_cbor_encoder = &GetAssertionEncoder(), | |
| .claim_cbor_encoder = &GetClaimCborEncoder(), | |
| .hash_factory = &hash_factory_, | |
| }); | |
| ABSL_CHECK_OK(manifest_encoder); | |
| manifest_encoder_ = std::move(*manifest_encoder); | |
| } | |
| std::unique_ptr<CryptoWriteHandler> crypto_writer_; | |
| std::unique_ptr<CryptoReadHandler> crypto_reader_; | |
| std::unique_ptr<ClaimSigner> claim_signer_; | |
| GeneratorInfo generator_info_; | |
| Sha256HasherFactory hash_factory_; | |
| std::unique_ptr<ManifestEncoder> manifest_encoder_; | |
| }; | |
| TEST_F(ManifestEncoderTest, EncodeManifest) { | |
| ManifestBox manifest_box = { | |
| .label = "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a", | |
| .instance_id = "12345678-1234-1234-1234-123456789012", | |
| .hard_binding_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.hash.bmff.v3" | |
| bmff_based_hash { exclusions { xpath: "" } } | |
| )pb"), | |
| .actions_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { | |
| action: "c2pa.created" | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| } | |
| )pb"), | |
| .ingredient_assertions = {}, | |
| .manifest_store_builder = ManifestStoreBuilder(&hash_factory_), | |
| }; | |
| Claim expected_claim = ParseTextProtoOrDie(R"pb( | |
| instance_id: "12345678-1234-1234-1234-123456789012" | |
| claim_generator_info { name: "Test generator" } | |
| signature_info { | |
| cert_serial_number: "02" | |
| issuer: "OU=Test Unit,O=Test Org,CN=Test Claim Signer" | |
| certificate_issuer: "OU=Test Unit,O=Test Org,CN=Test Root CA" | |
| timestamping_time {} | |
| } | |
| created_assertions { | |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.bmff.v3" | |
| algorithm: "sha256" | |
| hash: "\016^\204(\332\347\371\222!\343x\220d\3607&7\237\332\211ps\004\231\222q\362\021\2261\330\261" | |
| } | |
| created_assertions { | |
| url: "self#jumbf=c2pa.assertions/c2pa.actions.v2" | |
| algorithm: "sha256" | |
| hash: "\331\271\373\3228\310\335>y\263\334\342\335\367\211Q\360\325\227\r\345$]\214\005\3047[\r#1\312" | |
| } | |
| default_algorithm: "sha256" | |
| label: "c2pa.claim.v2" | |
| )pb"); | |
| PartialValidationResultProto expected_validated_result = ParseTextProtoOrDie( | |
| R"pb( | |
| active_manifest { | |
| label: "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a" | |
| claim { | |
| claim_generator_info { name: "Test generator" } | |
| signature_info { | |
| cert_serial_number: "02" | |
| issuer: "OU=Test Unit,O=Test Org,CN=Test Claim Signer" | |
| certificate_issuer: "OU=Test Unit,O=Test Org,CN=Test Root CA" | |
| timestamping_time {} | |
| } | |
| created_assertions { | |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.bmff.v3" | |
| algorithm: "sha256" | |
| hash: "\016^\204(\332\347\371\222!\343x\220d\3607&7\237\332\211ps\004\231\222q\362\021\2261\330\261" | |
| } | |
| created_assertions { | |
| url: "self#jumbf=c2pa.assertions/c2pa.actions.v2" | |
| algorithm: "sha256" | |
| hash: "\331\271\373\3228\310\335>y\263\334\342\335\367\211Q\360\325\227\r\345$]\214\005\3047[\r#1\312" | |
| } | |
| default_algorithm: "sha256" | |
| label: "c2pa.claim.v2" | |
| instance_id: "12345678-1234-1234-1234-123456789012" | |
| } | |
| assertions { | |
| label: "c2pa.hash.bmff.v3" | |
| bmff_based_hash { exclusions { xpath: "" } } | |
| } | |
| assertions { | |
| label: "c2pa.actions.v2" | |
| actions { | |
| actions { | |
| action: "c2pa.created" | |
| description: "created for testing" | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| software_agent { name: "test generator" } | |
| } | |
| } | |
| } | |
| validation { | |
| successes { | |
| code: "timeStamp.validated" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| successes { | |
| code: "timeStamp.trusted" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| successes { | |
| code: "signingCredential.trusted" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| successes { | |
| code: "claimSignature.insideValidity" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| successes { | |
| code: "claimSignature.validated" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| successes { | |
| code: "assertion.hashedURI.match" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.assertions/c2pa.hash.bmff.v3" | |
| } | |
| successes { | |
| code: "assertion.hashedURI.match" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.assertions/c2pa.actions.v2" | |
| } | |
| informationals { | |
| code: "signingCredential.ocsp.skipped" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| } | |
| } | |
| hard_binding_uri: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.assertions/c2pa.hash.bmff.v3" | |
| )pb"); | |
| absl::StatusOr<jumbf::BuiltSuperBox> built_manifest_store_box = | |
| Encode(*manifest_encoder_, manifest_box); | |
| ABSL_ASSERT_OK(built_manifest_store_box); | |
| absl::Cord result_manifest_store_bytes = | |
| std::move(*built_manifest_store_box).AsCord(); | |
| EXPECT_THAT( | |
| ValidateClaim(result_manifest_store_bytes.Flatten()), | |
| IsOkAndHolds(EqualsProtoIgnoringTimestampAndOrdering(expected_claim))); | |
| EXPECT_THAT(ValidateManifestStore(result_manifest_store_bytes.Flatten()), | |
| IsOkAndHolds(Pointee(EqualsProtoIgnoringTimestampAndOrdering( | |
| expected_validated_result)))); | |
| } | |
| TEST_F(ManifestEncoderTest, EncodeManifestWithAllActionsIncluded) { | |
| ManifestBox manifest_box = { | |
| .label = "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a", | |
| .instance_id = "12345678-1234-1234-1234-123456789012", | |
| .hard_binding_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.hash.bmff.v3" | |
| bmff_based_hash { exclusions { xpath: "" } } | |
| )pb"), | |
| .actions_assertion = ParseTextProtoOrDie(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { | |
| all_actions_included: true | |
| actions { | |
| action: "c2pa.created" | |
| software_agent { name: "test generator" } | |
| description: "created for testing" | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| } | |
| } | |
| )pb"), | |
| .ingredient_assertions = {}, | |
| .manifest_store_builder = ManifestStoreBuilder(&hash_factory_), | |
| }; | |
| Claim expected_claim = ParseTextProtoOrDie(R"pb( | |
| instance_id: "12345678-1234-1234-1234-123456789012" | |
| claim_generator_info { name: "Test generator" } | |
| signature_info { | |
| cert_serial_number: "02" | |
| issuer: "OU=Test Unit,O=Test Org,CN=Test Claim Signer" | |
| certificate_issuer: "OU=Test Unit,O=Test Org,CN=Test Root CA" | |
| timestamping_time {} | |
| } | |
| created_assertions { | |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.bmff.v3" | |
| algorithm: "sha256" | |
| hash: "\016^\204(\332\347\371\222!\343x\220d\3607&7\237\332\211ps\004\231\222q\362\021\2261\330\261" | |
| } | |
| created_assertions { | |
| url: "self#jumbf=c2pa.assertions/c2pa.actions.v2" | |
| algorithm: "sha256" | |
| hash: "\332\t\376b\010\364\"\024\206\261\340\007Y\255<\361\257\277A\205Q\371\251k\004\227\202\004Ie\237\034" | |
| } | |
| default_algorithm: "sha256" | |
| label: "c2pa.claim.v2" | |
| )pb"); | |
| PartialValidationResultProto expected_validated_result = ParseTextProtoOrDie( | |
| R"pb( | |
| active_manifest { | |
| label: "urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a" | |
| claim { | |
| claim_generator_info { name: "Test generator" } | |
| signature_info { | |
| cert_serial_number: "02" | |
| issuer: "OU=Test Unit,O=Test Org,CN=Test Claim Signer" | |
| certificate_issuer: "OU=Test Unit,O=Test Org,CN=Test Root CA" | |
| timestamping_time {} | |
| } | |
| created_assertions { | |
| url: "self#jumbf=c2pa.assertions/c2pa.hash.bmff.v3" | |
| algorithm: "sha256" | |
| hash: "\016^\204(\332\347\371\222!\343x\220d\3607&7\237\332\211ps\004\231\222q\362\021\2261\330\261" | |
| } | |
| created_assertions { | |
| url: "self#jumbf=c2pa.assertions/c2pa.actions.v2" | |
| algorithm: "sha256" | |
| hash: "\332\t\376b\010\364\"\024\206\261\340\007Y\255<\361\257\277A\205Q\371\251k\004\227\202\004Ie\237\034" | |
| } | |
| default_algorithm: "sha256" | |
| label: "c2pa.claim.v2" | |
| instance_id: "12345678-1234-1234-1234-123456789012" | |
| } | |
| assertions { | |
| label: "c2pa.hash.bmff.v3" | |
| bmff_based_hash { exclusions { xpath: "" } } | |
| } | |
| assertions { | |
| label: "c2pa.actions.v2" | |
| actions { | |
| all_actions_included: true | |
| actions { | |
| action: "c2pa.created" | |
| description: "created for testing" | |
| digital_source_type: "https://cv.iptc.org/newscodes/digitalsourcetype/computationalCapture" | |
| software_agent { name: "test generator" } | |
| } | |
| } | |
| } | |
| validation { | |
| successes { | |
| code: "timeStamp.validated" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| successes { | |
| code: "timeStamp.trusted" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| successes { | |
| code: "signingCredential.trusted" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| successes { | |
| code: "claimSignature.insideValidity" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| successes { | |
| code: "claimSignature.validated" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| successes { | |
| code: "assertion.hashedURI.match" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.assertions/c2pa.hash.bmff.v3" | |
| } | |
| successes { | |
| code: "assertion.hashedURI.match" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.assertions/c2pa.actions.v2" | |
| } | |
| informationals { | |
| code: "signingCredential.ocsp.skipped" | |
| url: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.signature" | |
| } | |
| } | |
| } | |
| hard_binding_uri: "self#jumbf=/c2pa/urn:c2pa:03020100-0504-0706-0908-0f0e0d0c0b0a/c2pa.assertions/c2pa.hash.bmff.v3" | |
| )pb"); | |
| absl::StatusOr<jumbf::BuiltSuperBox> built_manifest_store_box = | |
| Encode(*manifest_encoder_, manifest_box); | |
| ABSL_ASSERT_OK(built_manifest_store_box); | |
| absl::Cord result_manifest_store_bytes = | |
| std::move(*built_manifest_store_box).AsCord(); | |
| EXPECT_THAT( | |
| ValidateClaim(result_manifest_store_bytes.Flatten()), | |
| IsOkAndHolds(EqualsProtoIgnoringTimestampAndOrdering(expected_claim))); | |
| EXPECT_THAT(ValidateManifestStore(result_manifest_store_bytes.Flatten()), | |
| IsOkAndHolds(Pointee(EqualsProtoIgnoringTimestampAndOrdering( | |
| expected_validated_result)))); | |
| } | |
| } // namespace | |
| } // namespace credentio |