blob: 0e95bb027f09f51302492c21e73965acf8a82fea [file] [edit]
// 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/zip/hasher_data.h"
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/status_matchers.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "bindings/collection_data_hash_hard_binding_validator.h"
#include "constants/status_codes.h"
#include "crypto/algorithms.h"
#include "formats/zip/reader.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "proto/collection_data_hash_assertion.pb.h"
#include "riegeli/bytes/reader.h"
#include "testing/proto_test_utils.h"
#include "testing/test_file_utils.h"
#include "testing/test_validation_tracker.h"
#include "utils/status_tracker.h"
#include "validator/tracker.h"
namespace credentio {
namespace {
using ::absl_testing::StatusIs;
using ::credentio_testing::EqualsProto;
using ::testing::HasSubstr;
using ::testing::IsEmpty;
using ::testing::SizeIs;
// A StatusTracker that records to a ValidationTracker.
class RecordingStatusTracker : public StatusTracker {
public:
explicit RecordingStatusTracker(ValidationTracker& tracker)
: tracker_(tracker) {}
void RecordSuccess(SuccessStatusCode code,
ValidationTracker::RecordOptions options) override {
tracker_.RecordSuccess(code, options);
}
void RecordFailure(FailureStatusCode code,
ValidationTracker::RecordOptions options) override {
tracker_.RecordFailure(code, options);
}
void RecordInformational(InformationalStatusCode code,
ValidationTracker::RecordOptions options) override {
tracker_.RecordInformational(code, options);
}
ValidationTracker& tracker_;
};
class DigestCollectionDataHashTest : public testing::Test {
protected:
static void SetUpTestSuite() {
// This ZIP archive contains three files: "file_1.txt", "file_2.txt" and a
// fake manifest.
auto file_reader = credentio_testing::GetFileReader(
"credentio/formats/zip/testing/"
"asset_fake_c2pa.zip");
ABSL_ASSERT_OK(file_reader);
source_ = file_reader->release();
auto reader = ZipReader::Create(source_);
ABSL_ASSERT_OK(reader);
central_directory_offset_ = (*reader)->central_directory_offset();
file_entries_ = new std::vector<ZipReader::FileEntry>();
while ((*reader)->HasNext()) {
absl::StatusOr<ZipReader::FileEntry> entry = (*reader)->Next();
ABSL_ASSERT_OK(entry);
file_entries_->push_back(*entry);
}
}
static void TearDownTestSuite() {
delete file_entries_;
file_entries_ = nullptr;
delete source_;
source_ = nullptr;
}
inline static riegeli::Reader* source_ = nullptr;
inline static std::vector<ZipReader::FileEntry>* file_entries_ = nullptr;
inline static uint64_t central_directory_offset_ = 0;
};
TEST_F(DigestCollectionDataHashTest, TemplateMatchesFinal) {
absl::StatusOr<CollectionDataHashAssertion> template_assertion =
CollectionDataHashTemplate(*file_entries_, HashAlgorithm::kSha256);
ABSL_ASSERT_OK(template_assertion);
absl::StatusOr<CollectionDataHashAssertion> final_assertion =
DigestCollectionDataHash(source_, HashAlgorithm::kSha256, *file_entries_,
central_directory_offset_);
ABSL_ASSERT_OK(final_assertion);
EXPECT_THAT(final_assertion->zip_central_directory_hash(),
SizeIs(template_assertion->zip_central_directory_hash().size()));
EXPECT_THAT(final_assertion->uris(), SizeIs(template_assertion->uris_size()));
// Copy values from final assertion to template assertion and compare.
for (int i = 0; i < final_assertion->uris_size(); ++i) {
template_assertion->mutable_uris(i)->set_hash(
final_assertion->uris(i).hash());
}
template_assertion->set_zip_central_directory_hash(
final_assertion->zip_central_directory_hash());
EXPECT_THAT(*final_assertion, EqualsProto(*template_assertion));
}
TEST_F(DigestCollectionDataHashTest, BindingValidates) {
absl::StatusOr<CollectionDataHashAssertion> final_assertion =
DigestCollectionDataHash(source_, HashAlgorithm::kSha256, *file_entries_,
central_directory_offset_);
ABSL_ASSERT_OK(final_assertion);
ASSERT_TRUE(source_->Seek(0));
CollectionDataHashHardBindingValidator validator;
TestValidationTracker t;
RecordingStatusTracker tracker(t.tracker());
validator.Validate(*source_, *final_assertion, "test_uri", tracker);
EXPECT_THAT(
t.GetSuccesses(),
ContainsSuccess(SuccessStatusCode::kAssertionCollectionHashMatch));
EXPECT_THAT(t.GetFailures(), IsEmpty());
EXPECT_THAT(t.GetInformationals(), IsEmpty());
}
TEST_F(DigestCollectionDataHashTest,
DigestCollectionDataHashFailsWithDuplicateEntries) {
ASSERT_GE(file_entries_->size(), 1);
std::vector<ZipReader::FileEntry> duplicate_entries = *file_entries_;
duplicate_entries.push_back((*file_entries_)[0]);
EXPECT_THAT(
DigestCollectionDataHash(source_, HashAlgorithm::kSha256,
duplicate_entries, central_directory_offset_),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Overlapping or duplicate ZIP entries detected")));
}
} // namespace
} // namespace credentio