| // 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 "testing/jumbf_utils.h" | |
| #include <cstdint> | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <vector> | |
| #include "absl/log/absl_check.h" | |
| #include "absl/log/check.h" | |
| #include "absl/status/status.h" | |
| #include "absl/status/status_macros.h" | |
| #include "absl/status/status_matchers.h" // IWYU pragma: keep | |
| #include "absl/strings/string_view.h" | |
| #include "absl/types/span.h" | |
| #include "assertion/assertion_cbor_encoder.h" | |
| #include "constants/labels.h" | |
| #include "crypto/default/hasher.h" | |
| #include "crypto/hash.h" | |
| #include "jumbf/box.h" | |
| #include "jumbf/constants.h" | |
| #include "jumbf/parse.h" | |
| #include "jumbf/test_utils.h" | |
| #include "proto/assertion.pb.h" | |
| #include "utils/byte_readers.h" | |
| #include "utils/byte_writers.h" | |
| #include "uuid/uuid.h" | |
| namespace credentio { | |
| namespace { | |
| absl::StatusOr<absl::string_view> TrimJumbfHeader(absl::string_view raw_bytes) { | |
| ABSL_ASSIGN_OR_RETURN(auto lbox, ConsumeUint<uint32_t>(&raw_bytes)); | |
| ABSL_RETURN_IF_ERROR(SkipBytes<uint32_t>(&raw_bytes)); | |
| if (lbox == 1) { | |
| auto xlbox_status = SkipBytes<uint64_t>(&raw_bytes); | |
| ABSL_RETURN_IF_ERROR(xlbox_status); | |
| } | |
| return raw_bytes; | |
| } | |
| std::string ComputeHashOrDie(absl::string_view algorithm, | |
| absl::string_view contents) { | |
| auto hasher = CreateHasher(algorithm); | |
| ABSL_CHECK_OK(hasher) << "Unsupported algorithm: " << algorithm; | |
| (*hasher)->Update(contents); | |
| return (*hasher)->Digest(); | |
| } | |
| } // namespace | |
| jumbf::SuperBox ParseSuperBoxOrDie(const std::string* bytes) { | |
| absl::string_view bytes_view = *bytes; | |
| auto box = jumbf::ConsumeSuperBox(&bytes_view, /*recursion_limit=*/-1); | |
| ABSL_CHECK_OK(box); | |
| ABSL_CHECK_EQ(bytes_view, ""); | |
| return *box; | |
| } | |
| jumbf::SuperBox CreateManifestStore( | |
| absl::Span<const jumbf::SuperBox> manifests) { | |
| jumbf::SuperBox box = { | |
| .description = | |
| { | |
| .type_uuid = kManifestStoreUuid, | |
| .label = kManifestStoreLabel, | |
| }, | |
| .raw_bytes = "b64'testing'", | |
| }; | |
| for (const auto& m : manifests) { | |
| box.contents.push_back({.payload = m}); | |
| } | |
| return box; | |
| } | |
| jumbf::SuperBox CreateStandardManifest( | |
| absl::string_view label, absl::Span<const jumbf::SuperBox> boxes) { | |
| jumbf::SuperBox box = { | |
| .description = | |
| { | |
| .type_uuid = kStandardManifestUuid, | |
| .label = label, | |
| }, | |
| .raw_bytes = "b64'testing'", | |
| }; | |
| for (const auto& b : boxes) { | |
| box.contents.push_back({.payload = b}); | |
| } | |
| return box; | |
| } | |
| jumbf::SuperBox CreateUpdateManifest(absl::string_view label, | |
| absl::Span<const jumbf::SuperBox> boxes) { | |
| jumbf::SuperBox box = { | |
| .description = | |
| { | |
| .type_uuid = kUpdateManifestUuid, | |
| .label = label, | |
| }, | |
| .raw_bytes = "b64'testing'", | |
| }; | |
| for (const auto& b : boxes) { | |
| box.contents.push_back({.payload = b}); | |
| } | |
| return box; | |
| } | |
| std::string EncodeClaimSignatureBox(absl::string_view cbor) { | |
| return jumbf::EncodeSuperBox(kClaimSignatureUuid, kClaimSignatureLabel, | |
| {jumbf::EncodeCborBox(cbor)}); | |
| } | |
| std::string EncodeAssertionBoxOrDie(const Assertion& assertion, | |
| const AssertionCborEncoder& encoder) { | |
| auto assertion_cbor = encoder.ToCbor(assertion); | |
| ABSL_CHECK_OK(assertion_cbor); | |
| std::string assertion_cbor_box = | |
| jumbf::EncodeCborBox(std::string(*assertion_cbor)); | |
| return jumbf::EncodeSuperBox(jumbf::kCborBoxTypeUuid, assertion.label(), | |
| {assertion_cbor_box}, /*requestable=*/true); | |
| } | |
| jumbf::SuperBox CreateAssertionStoreBox() { | |
| return { | |
| .description = | |
| { | |
| .type_uuid = kAssertionStoreUuid, | |
| .label = kAssertionStoreLabel, | |
| }, | |
| .raw_bytes = "b64'testing'", | |
| }; | |
| } | |
| jumbf::SuperBox CreateAssertionStoreBox( | |
| absl::Span<const jumbf::SuperBox> assertions) { | |
| auto assertion_store_box = CreateAssertionStoreBox(); | |
| for (const auto& assertion : assertions) { | |
| assertion_store_box.contents.push_back({.payload = assertion}); | |
| } | |
| return assertion_store_box; | |
| } | |
| jumbf::SuperBox CreateAssertionBoxFromCbor(absl::string_view label, | |
| absl::string_view cbor) { | |
| return { | |
| .description = {.label = label}, | |
| .contents = {jumbf::ContentBox{.payload = | |
| jumbf::CborBox{.payload = cbor}}}, | |
| .raw_bytes = "b64'testing'", | |
| }; | |
| } | |
| jumbf::SuperBox CreateAssertionBoxFromJson(absl::string_view label, | |
| absl::string_view json) { | |
| return { | |
| .description = {.label = label}, | |
| .contents = {jumbf::ContentBox{.payload = | |
| jumbf::JsonBox{.payload = json}}}, | |
| .raw_bytes = "b64'testing'", | |
| }; | |
| } | |
| jumbf::SuperBox CreateAssertionBoxFromEmbeddedFile( | |
| absl::string_view label, absl::string_view media_type, | |
| absl::string_view file_contents, | |
| std::optional<absl::string_view> file_name) { | |
| return { | |
| .description = {.label = label}, | |
| .contents = {jumbf::ContentBox{.payload = | |
| jumbf::EmbeddedFileDescriptionBox{ | |
| .media_type = media_type, | |
| .file_name = file_name}}, | |
| jumbf::ContentBox{ | |
| .payload = | |
| jumbf::BinaryDataBox{.payload = file_contents}}}, | |
| .raw_bytes = "b64'testing'", | |
| }; | |
| } | |
| jumbf::SuperBox CreateActionsAssertionBoxFromCbor(absl::string_view cbor) { | |
| return CreateAssertionBoxFromCbor(kActionsAssertionV2Label, cbor); | |
| } | |
| jumbf::SuperBox CreateBmffBasedHashAssertionBoxFromCbor( | |
| absl::string_view cbor) { | |
| return CreateAssertionBoxFromCbor(kBmffBasedHashAssertionV3Label, cbor); | |
| } | |
| jumbf::SuperBox CreateDataHashAssertionBoxFromCbor(absl::string_view cbor) { | |
| return CreateAssertionBoxFromCbor(kDataHashAssertionLabel, cbor); | |
| } | |
| std::string GetHash(absl::string_view algorithm, const jumbf::SuperBox& box) { | |
| auto raw_bytes = TrimJumbfHeader(box.raw_bytes); | |
| ABSL_CHECK_OK(raw_bytes); | |
| return ComputeHashOrDie(algorithm, *raw_bytes); | |
| } | |
| std::string CreateStartOfManifestStorePayload( | |
| absl::Span<const uint8_t> prefix) { | |
| std::vector<uint8_t> bytes; | |
| bytes.reserve(prefix.size() + 40); | |
| for (const auto& byte : prefix) { | |
| bytes.push_back(byte); | |
| } | |
| WriteUint32NetworkOrder(10, &bytes); | |
| WriteUint32NetworkOrder(jumbf::kSuperBoxType, &bytes); | |
| WriteUint32NetworkOrder(10, &bytes); | |
| WriteUint32NetworkOrder(jumbf::kDescriptionBoxType, &bytes); | |
| WriteUint64NetworkOrder(0x6332'7061'0011'0010, &bytes); | |
| WriteUint64NetworkOrder(0x8000'00AA'0038'9B71, &bytes); | |
| bytes.push_back(0b0000'0011); | |
| WriteUint32NetworkOrder('c2pa', &bytes); | |
| bytes.push_back(0x00); | |
| return std::string(reinterpret_cast<const char*>(bytes.data()), bytes.size()); | |
| } | |
| } // namespace credentio |