blob: 84845005e66f1c45bb48dc24a6788d483960b5d8 [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 "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