blob: 9b9a2907f78b9646d0d1a21d3805f6a16335c126 [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 "formats/riff/hasher_box.h"
#include <string>
#include "absl/status/status.h"
#include "absl/status/status_matchers.h"
#include "absl/strings/str_cat.h"
#include "crypto/algorithms.h"
#include "formats/riff/create_riff.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "proto/boxes_hash_assertion.pb.h"
#include "riegeli/bytes/string_reader.h"
#include "testing/proto_test_utils.h"
namespace credentio {
namespace {
using ::absl_testing::IsOkAndHolds;
using ::absl_testing::StatusIs;
using ::credentio_riff::Chunk;
using ::credentio_riff::RiffChunk;
using ::credentio_testing::EqualsProto;
using ::credentio_testing::ParseTextProtoOrDie;
using ::testing::HasSubstr;
TEST(DigestRiffBoxHashTest, NoC2pa) {
std::string contents = RiffChunk("WEBP", Chunk("VP8 ", "aaaaaaaa"));
riegeli::StringReader<> input(contents);
BoxesHashAssertion expected = ParseTextProtoOrDie<BoxesHashAssertion>(R"pb(
alg: "sha256"
boxes {
names: "RIFF"
names: "VP8 "
hash: "\227\216\222\217z)\256\345\271\257\273\013*;\264^e\t\2507I\340\022A\333\212\203%\210\rtc"
}
# C2PA placeholder is inserted at the end of the RIFF chunk.
boxes { names: "C2PA" hash: "\000" }
)pb");
EXPECT_THAT(DigestRiffBoxHash(input, HashAlgorithm::kSha256),
IsOkAndHolds(EqualsProto(expected)));
}
TEST(DigestRiffBoxHashTest, WithC2pa) {
std::string contents = RiffChunk(
"WEBP",
absl::StrCat(Chunk("VP8 ", "aaaaaaaa"), Chunk("C2PA", "manifest store")));
riegeli::StringReader<> input(contents);
BoxesHashAssertion expected = ParseTextProtoOrDie<BoxesHashAssertion>(R"pb(
alg: "sha256"
boxes {
names: "RIFF"
names: "VP8 "
hash: "V\216\345\371\376\247\347Dl\221\234W\264%\016\233\023d\342i\205\004\3021@\035\273\234\337\271)\343"
}
boxes { names: "C2PA" hash: "\000" }
)pb");
EXPECT_THAT(DigestRiffBoxHash(input, HashAlgorithm::kSha256),
IsOkAndHolds(EqualsProto(expected)));
}
TEST(DigestRiffBoxHashTest, EmptyRiff) {
riegeli::StringReader<> input("");
EXPECT_THAT(DigestRiffBoxHash(input, HashAlgorithm::kSha256),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("RIFF chunk not found")));
}
TEST(DigestRiffBoxHashTest, FirstChunkNotRiff) {
std::string contents = Chunk("ABCD", "aaaaaaaa");
riegeli::StringReader<> input(contents);
EXPECT_THAT(DigestRiffBoxHash(input, HashAlgorithm::kSha256),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("RIFF chunk not found")));
}
TEST(DigestRiffBoxHashTest, MultipleRiffChunks) {
std::string contents =
absl::StrCat(RiffChunk("WEBP", absl::StrCat(Chunk("ABCD", "aaaaaaaa"),
Chunk("EFGH", "bbbbbbbb"))),
RiffChunk("BLAH", Chunk("IJKL", "cccccccccccc")));
riegeli::StringReader<> input(contents);
BoxesHashAssertion expected = ParseTextProtoOrDie<BoxesHashAssertion>(R"pb(
alg: "sha256"
boxes {
names: "RIFF"
names: "ABCD"
names: "EFGH"
hash: "\323\224Q\237\376a@\373\230_f\354\204\376\255}\016\366\2724\377\200\304\260\002\373\264\332S\300\337\375"
}
boxes { names: "C2PA" hash: "\000" }
boxes {
names: "RIFF"
names: "IJKL"
hash: "\375\032\251\2252\227\265\201_\351\240M\236\307a8\303\245,\037w\362\271\276\024j{HJ\323q\314"
}
)pb");
EXPECT_THAT(DigestRiffBoxHash(input, HashAlgorithm::kSha256),
IsOkAndHolds(EqualsProto(expected)));
}
TEST(DigestRiffBoxHashTest, C2paInSecondRiffChunk) {
std::string contents = absl::StrCat(
RiffChunk("WEBP", absl::StrCat(Chunk("ABCD", "aaaaaaaa"),
Chunk("EFGH", "bbbbbbbb"))),
RiffChunk("BLAH", Chunk("C2PA",
"C2PA Manifest Store that should have been in "
"the first RIFF chunk")));
riegeli::StringReader<> input(contents);
EXPECT_THAT(DigestRiffBoxHash(input, HashAlgorithm::kSha256),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Unexpected C2PA chunk found")));
}
TEST(DigestRiffBoxHashTest, C2paInMiddleOfRiffChunk) {
std::string contents =
RiffChunk("WEBP", absl::StrCat(Chunk("ABCD", "aaaaaaaa"),
Chunk("C2PA", "manifest store"),
Chunk("EFGH", "bbbbbbbb")));
riegeli::StringReader<> input(contents);
EXPECT_THAT(DigestRiffBoxHash(input, HashAlgorithm::kSha256),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Expected RIFF chunk, found 'EFGH'")));
}
} // namespace
} // namespace credentio