| // Copyright 2026 Google LLC | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| // | |
| #include "assertion/assertion_cbor_encoder.h" | |
| #include <string> | |
| #include "absl/status/status.h" | |
| #include "absl/status/status_matchers.h" | |
| #include "cbor/cbor.h" | |
| #include "cbor/parse.h" | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| #include "proto/actions_assertion.cbor.h" | |
| #include "proto/actions_assertion.pb.h" | |
| #include "proto/assertion.pb.h" | |
| #include "proto/bmff_based_hash_assertion.cbor.h" | |
| #include "proto/bmff_based_hash_assertion.pb.h" | |
| #include "proto/boxes_hash_assertion.cbor.h" | |
| #include "proto/boxes_hash_assertion.pb.h" | |
| #include "proto/data_hash_assertion.cbor.h" | |
| #include "proto/data_hash_assertion.pb.h" | |
| #include "proto/ingredient_assertion.cbor.h" | |
| #include "proto/ingredient_assertion.pb.h" | |
| #include "testing/proto_test_utils.h" | |
| namespace credentio { | |
| namespace { | |
| using ::absl_testing::IsOk; | |
| using ::absl_testing::StatusIs; | |
| using ::cbor::Parse; | |
| using ::credentio_testing::EqualsProto; | |
| using ::credentio_testing::ParseTextProtoOrDie; | |
| using ::testing::HasSubstr; | |
| TEST(AssertionCborEncoderTest, UnsupportedAssertionToEncode) { | |
| auto assertion = ParseTextProtoOrDie<Assertion>(R"pb( | |
| label: "unsupported_assertion" | |
| )pb"); | |
| StandardAssertionCborEncoder encoder; | |
| EXPECT_THAT(encoder.ToCbor(assertion), | |
| StatusIs(absl::StatusCode::kInvalidArgument, | |
| HasSubstr("Assertion not supported."))); | |
| } | |
| TEST(AssertionCborEncoderTest, EncodeAction) { | |
| auto assertion = ParseTextProtoOrDie<Assertion>(R"pb( | |
| label: "c2pa.actions.v2" | |
| actions { | |
| all_actions_included: true | |
| actions { | |
| action: "c2pa.created" | |
| digital_source_type: "http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia" | |
| software_agent { | |
| name: "Joe\'s Photo Editor" | |
| version: "2.0" | |
| operating_system: "Windows 10" | |
| } | |
| parameters { | |
| instance_id: "ed610ae51f604002be3dbf0c589a2f1f" | |
| ingredients { | |
| url: "self#jumbf=c2pa/joe-ed:urn:uuid:ABCD/c2pa.assertions/c2pa.ingredient__1" | |
| algorithm: "sha256" | |
| hash: "hash" | |
| } | |
| ingredients { | |
| url: "self#jumbf=c2pa/joe-ed:urn:uuid:EFGH/c2pa.assertions/c2pa.ingredient__2" | |
| algorithm: "sha256" | |
| hash: "hash" | |
| } | |
| } | |
| } | |
| actions { | |
| action: "c2pa.filtered" | |
| software_agent { | |
| name: "Joe\'s Photo Editor" | |
| version: "2.0" | |
| operating_system: "Windows 10" | |
| } | |
| } | |
| actions { action: "c2pa.cropped", software_agent_index: 1 } | |
| templates { | |
| action: "com.joesphoto.filter" | |
| description: "Magic Filter" | |
| digital_source_type: "http://cv.iptc.org/newscodes/digitalsourcetype/compositeSynthetic" | |
| software_agent { name: "Joe\'s Photo Editor" version: "2.0" } | |
| icon { url: "http://cdn.examplephotoagency.com/logo.svg" hash: "hash" } | |
| } | |
| software_agents { | |
| name: "Joe\'s Photo Editor" | |
| version: "2.0" | |
| operating_system: "Windows 10" | |
| } | |
| software_agents { | |
| name: "Bob\'s Photo Editor" | |
| version: "1.0" | |
| operating_system: "Windows 10" | |
| } | |
| } | |
| )pb"); | |
| StandardAssertionCborEncoder encoder; | |
| auto cbor = encoder.ToCbor(assertion); | |
| ABSL_ASSERT_OK(cbor); | |
| std::string cbor_string(*cbor); | |
| auto parse_result = Parse(cbor_string); | |
| ABSL_ASSERT_OK(parse_result); | |
| auto map = (*parse_result)->AsMap(); | |
| ABSL_ASSERT_OK(map); | |
| credentio::ActionsAssertion result; | |
| auto status = cbor::ToProto(*map, &result); | |
| ABSL_ASSERT_OK(status); | |
| EXPECT_THAT(result, EqualsProto(assertion.actions())); | |
| } | |
| TEST(AssertionCborEncoderTest, EncodeBmffBasedHash) { | |
| auto assertion = ParseTextProtoOrDie<Assertion>(R"pb( | |
| label: "c2pa.hash.bmff.v3" | |
| bmff_based_hash { | |
| hash: "Zm9v", | |
| name: "Example `c2pa.hash.bmff.v2` assertion", | |
| exclusions: | |
| [ { | |
| data: | |
| [ { value: "Zm9v", offset: 8 }], | |
| xpath: "/uuid" | |
| } | |
| , { xpath: "/ftyp" } | |
| , { xpath: "/mfra" } | |
| , { xpath: "/moov[1]/pssh" } | |
| , { | |
| data: | |
| [ { value: "Zm9v", offset: 20 }], | |
| xpath: "/emsg" | |
| }] | |
| } | |
| )pb"); | |
| StandardAssertionCborEncoder encoder; | |
| auto cbor = encoder.ToCbor(assertion); | |
| ABSL_ASSERT_OK(cbor); | |
| std::string cbor_string(*cbor); | |
| auto parse_result = Parse(cbor_string); | |
| ABSL_ASSERT_OK(parse_result); | |
| auto map = (*parse_result)->AsMap(); | |
| ABSL_ASSERT_OK(map); | |
| credentio::BmffBasedHashAssertion result; | |
| auto status = cbor::ToProto(*map, &result); | |
| ABSL_ASSERT_OK(status); | |
| EXPECT_THAT(result, EqualsProto(assertion.bmff_based_hash())); | |
| } | |
| TEST(AssertionCborEncoderTest, EncodeDataHash) { | |
| auto assertion = ParseTextProtoOrDie<Assertion>(R"pb( | |
| label: "c2pa.hash.data" | |
| data_hash { | |
| alg: "sha256", | |
| pad: "b64'Zm9v'", | |
| hash: "b64'Zm9v'", | |
| name: "JUMBF manifest", | |
| exclusions: | |
| [ { start: 9960, length: 4213 } | |
| , { start: 14173, length: 1000 }] | |
| } | |
| )pb"); | |
| StandardAssertionCborEncoder encoder; | |
| auto cbor = encoder.ToCbor(assertion); | |
| ABSL_ASSERT_OK(cbor); | |
| std::string cbor_string(*cbor); | |
| auto parse_result = Parse(cbor_string); | |
| ABSL_ASSERT_OK(parse_result); | |
| auto map = (*parse_result)->AsMap(); | |
| ABSL_ASSERT_OK(map); | |
| EXPECT_THAT(map->GetByteString("pad"), IsOk()); | |
| credentio::DataHashAssertion result; | |
| auto status = cbor::ToProto(*map, &result); | |
| ABSL_ASSERT_OK(status); | |
| auto expected = assertion.data_hash(); | |
| expected.clear_pad(); | |
| auto actual = result; | |
| actual.clear_pad(); | |
| EXPECT_THAT(actual, EqualsProto(expected)); | |
| } | |
| TEST(AssertionCborEncoderTest, EncodeIngredientAssertion) { | |
| auto assertion = ParseTextProtoOrDie<Assertion>(R"pb( | |
| label: "c2pa.ingredient.v3" | |
| ingredient_v3 { | |
| title: "image 1.jpg", | |
| media_type: "image/jpeg", | |
| thumbnail: { | |
| url: "self#jumbf=c2pa/urn:c2pa:F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4/c2pa.thumbnail.ingredient_1.jpg", | |
| hash: "b64'UjRAYWiAq4lfCRDmksWAlDJN/XtHHFFwMWymsZsm3j8='" | |
| }, | |
| relationship: "parentOf", | |
| active_manifest: { | |
| url: "self#jumbf=c2pa/urn:c2pa:5E7B01FC-4932-4BAB-AB32-D4F12A8AA322", | |
| hash: "b64'1kjJTO108b71cL95UxgfHD3eDgk9VrCedW8n3fYTRMk='" | |
| }, | |
| claim_signature: { | |
| url: "self#jumbf=c2pa/urn:c2pa:5E7B01FC-4932-4BAB-AB32-D4F12A8AA322/c2pa.signature", | |
| hash: "b64'85KAvU3+3YgtIjj6IV0fzKwj8si/85+gevVSK2Iw+S0='" | |
| }, | |
| validation_results: { | |
| active_manifest: { | |
| successes: | |
| [ { | |
| code: "claimSignature.validated", | |
| url: "self#jumbf=c2pa/urn:c2pa:5E7B01FC-4932-4BAB-AB32-D4F12A8AA322/c2pa.signature" | |
| } | |
| , { | |
| code: "signingCredential.trusted", | |
| url: "self#jumbf=c2pa/urn:c2pa:5E7B01FC-4932-4BAB-AB32-D4F12A8AA322/c2pa.signature" | |
| } | |
| , { | |
| code: "timeStamp.validated", | |
| url: "self#jumbf=c2pa/urn:c2pa:5E7B01FC-4932-4BAB-AB32-D4F12A8AA322/c2pa.signature" | |
| } | |
| , { | |
| code: "timeStamp.trusted", | |
| url: "self#jumbf=c2pa/urn:c2pa:5E7B01FC-4932-4BAB-AB32-D4F12A8AA322/c2pa.signature" | |
| } | |
| , { | |
| code: "assertion.hashedURI.match", | |
| url: "self#jumbf=c2pa/urn:c2pa:5E7B01FC-4932-4BAB-AB32-D4F12A8AA322/c2pa.assertions/c2pa.ingredient.v3" | |
| }], | |
| informationals: | |
| [ { | |
| code: "signingCredential.ocsp.skipped", | |
| url: "self#jumbf=c2pa/urn:c2pa:5E7B01FC-4932-4BAB-AB32-D4F12A8AA322/c2pa.signature" | |
| }], | |
| failures: [] | |
| }, | |
| ingredient_deltas: | |
| [ { | |
| ingredient_assertion_uri: "self#jumbf=c2pa/urn:c2pa:5E7B01FC-4932-4BAB-AB32-D4F12A8AA322/c2pa.assertions/c2pa.ingredient.v3", | |
| validation_deltas: { | |
| successes: [], | |
| informationals: [], | |
| failures: | |
| [ { | |
| code: "assertion.hashedURI.mismatch", | |
| url: "self#jumbf=c2pa/urn:c2pa:F095F30E-6CD5-4BF7-8C44-CE8420CA9FB7/c2pa.assertions/c2pa.metadata" | |
| }] | |
| } | |
| } | |
| , { | |
| ingredient_assertion_uri: "self#jumbf=c2pa/urn:c2pa:F095F30E-6CD5-4BF7-8C44-CE8420CA9FB7/c2pa.assertions/c2pa.ingredient.v3", | |
| validation_deltas: { | |
| successes: [], | |
| informationals: [], | |
| failures: | |
| [ { | |
| code: "signingCredential.untrusted", | |
| url: "self#jumbf=c2pa/urn:c2pa:72C28A7C-7F5B-4301-B373-3183C10AF7C5/c2pa.signature" | |
| }] | |
| } | |
| }] | |
| } | |
| } | |
| )pb"); | |
| StandardAssertionCborEncoder encoder; | |
| auto cbor = encoder.ToCbor(assertion); | |
| ABSL_ASSERT_OK(cbor); | |
| std::string cbor_string(*cbor); | |
| auto parse_result = Parse(cbor_string); | |
| ABSL_ASSERT_OK(parse_result); | |
| auto map = (*parse_result)->AsMap(); | |
| ABSL_ASSERT_OK(map); | |
| credentio::IngredientAssertionV3 result; | |
| auto status = cbor::ToProto(*map, &result); | |
| ABSL_ASSERT_OK(status); | |
| EXPECT_THAT(result, EqualsProto(assertion.ingredient_v3())); | |
| } | |
| TEST(AssertionCborEncoderTest, EncodeBoxesHash) { | |
| auto assertion = ParseTextProtoOrDie<Assertion>(R"pb( | |
| label: "c2pa.hash.boxes" | |
| boxes_hash { | |
| alg: "sha256", | |
| boxes: | |
| [ { names: [ "SOI", "APP0", "APP2" ], hash: "b64'Zm9v'" } | |
| , { names: [ "C2PA" ], hash: "b64'YmFy'" }] | |
| } | |
| )pb"); | |
| StandardAssertionCborEncoder encoder; | |
| auto cbor = encoder.ToCbor(assertion); | |
| ABSL_ASSERT_OK(cbor); | |
| std::string cbor_string(*cbor); | |
| auto parse_result = Parse(cbor_string); | |
| ABSL_ASSERT_OK(parse_result); | |
| auto map = (*parse_result)->AsMap(); | |
| ABSL_ASSERT_OK(map); | |
| credentio::BoxesHashAssertion result; | |
| auto status = cbor::ToProto(*map, &result); | |
| ABSL_ASSERT_OK(status); | |
| EXPECT_THAT(result, EqualsProto(assertion.boxes_hash())); | |
| } | |
| TEST(GetAssertionCborEncoderTest, ReturnsSameInstance) { | |
| EXPECT_EQ(&GetAssertionCborEncoder(), &GetAssertionCborEncoder()); | |
| } | |
| } // namespace | |
| } // namespace credentio |