blob: d776271bf965618d02d8d333a4301b3c6e7a8ba0 [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 "bindings/data_hash_hard_binding_validator.h"
#include <cstdint>
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include "absl/base/nullability.h"
#include "absl/log/die_if_null.h"
#include "absl/status/status.h"
#include "absl/status/status_macros.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "bindings/binding_hasher.h"
#include "constants/status_codes.h"
#include "formats/asset_byte_info.h"
#include "formats/byte_range.h"
#include "proto/assertion.pb.h"
#include "proto/data_hash_assertion.pb.h"
#include "proto/validation_result.pb.h"
#include "riegeli/bytes/reader.h"
#include "utils/dual_status_tracker.h"
#include "utils/status_tracker.h"
#include "validator/result.h"
#include "validator/validation_result_internal.h"
namespace credentio {
namespace {
class DataHashBindingHasherTracker : public BindingHasherTracker {
public:
explicit DataHashBindingHasherTracker(StatusTracker* tracker,
absl::string_view hard_binding_uri)
: tracker_(*ABSL_DIE_IF_NULL(tracker)),
hard_binding_uri_(hard_binding_uri) {}
void RecordMismatch() override {
tracker_.RecordFailure(FailureStatusCode::kAssertionDataHashMismatch,
{.url = hard_binding_uri_});
}
void RecordMalformed() override {
tracker_.RecordFailure(FailureStatusCode::kAssertionDataHashMalformed,
{.url = hard_binding_uri_});
}
void RecordAlgorithmUnsupported() override {
tracker_.RecordFailure(FailureStatusCode::kAlgorithmUnsupported,
{.url = hard_binding_uri_});
}
void RecordGeneralError(absl::Status error) override {
tracker_.RecordFailure(
FailureStatusCode::kGeneralError,
{.url = hard_binding_uri_, .explanation = error.message()});
}
private:
StatusTracker& tracker_;
absl::string_view hard_binding_uri_;
};
} // namespace
absl::StatusOr<std::unique_ptr<ValidationResultProto>>
DataHashHardBindingValidator::Validate(
riegeli::Reader& contents, const AssetByteInfo& asset_byte_info,
std::unique_ptr<PartialValidationResultProto> partial_validation_result)
const {
const Assertion* absl_nullable assertion =
GetAssertion(partial_validation_result.get(),
partial_validation_result->hard_binding_uri());
if (assertion == nullptr) {
return absl::InvalidArgumentError(absl::StrCat(
"missing assertion: ", partial_validation_result->hard_binding_uri()));
}
if (!assertion->has_data_hash()) {
return absl::InvalidArgumentError(
absl::StrCat("assertion is not a data hash assertion: ",
partial_validation_result->hard_binding_uri()));
}
ABSL_ASSIGN_OR_RETURN(auto tracker,
DualStatusTracker::FromPartialValidationResult(
partial_validation_result.get()));
Validate(contents, asset_byte_info.manifest_store_location,
assertion->data_hash(),
partial_validation_result->hard_binding_uri(), *tracker,
tracker->WritesToIngredientManifest());
return MakeFullValidationResult(std::move(partial_validation_result));
}
void DataHashHardBindingValidator::Validate(
riegeli::Reader& contents, std::optional<ByteRange> manifest_store_location,
const DataHashAssertion& assertion, absl::string_view hard_binding_uri,
StatusTracker& tracker, bool assertion_in_ingredient_manifest,
uint64_t start_offset, int64_t end_offset) const {
std::vector<BindingHasher::Chunk> exclusions;
if (!contents.SupportsSize() || !contents.Size().has_value()) {
tracker.RecordFailure(FailureStatusCode::kGoogleInternalError,
{.url = hard_binding_uri,
.explanation = "Asset size cannot be determined"});
return;
}
const int64_t c2pa_start = manifest_store_location.has_value()
? manifest_store_location->offset
: -1;
const uint64_t asset_end_offset =
end_offset == -1 ? *contents.Size() : end_offset;
uint64_t valid_next_exclusion_start = start_offset;
int64_t offset_adjustment = 0;
bool has_additional_exclusions = assertion.exclusions_size() > 1;
for (const auto& exclusion : assertion.exclusions()) {
if (exclusion.start() < 0 || exclusion.length() < 0) {
tracker.RecordFailure(FailureStatusCode::kAssertionDataHashMalformed,
{.url = hard_binding_uri});
return;
}
const uint64_t ex_start =
start_offset + exclusion.start() + offset_adjustment;
int64_t ex_length = exclusion.length();
if (assertion_in_ingredient_manifest && ex_start == c2pa_start) {
// Note: fluffy_puppy.webp manifest exclusion range is 1 byte shorter than
// the box and if this case is triggered, it would fail validation.
ex_length = manifest_store_location->length;
offset_adjustment = manifest_store_location->length - exclusion.length();
}
// ex_start is now with respect to the start of the file
if (ex_start < start_offset || exclusion.length() < 0) {
// Starts before the start of this asset or negative length.
tracker.RecordFailure(FailureStatusCode::kAssertionDataHashMalformed,
{.url = hard_binding_uri});
return;
}
if (ex_start < valid_next_exclusion_start) {
// Starts before the end of the previous exclusion range.
tracker.RecordFailure(FailureStatusCode::kAssertionDataHashMalformed,
{.url = hard_binding_uri});
return;
}
valid_next_exclusion_start = ex_start + ex_length;
if (ex_start + ex_length > asset_end_offset) {
// Ends past the end of the asset.
tracker.RecordFailure(FailureStatusCode::kAssertionDataHashMismatch,
{.url = hard_binding_uri});
return;
}
exclusions.push_back({.op = BindingHasher::Chunk::Op::kExclusion,
.offset = ex_start,
.length = ex_length});
if (ex_start < c2pa_start) {
has_additional_exclusions = true;
}
}
if (manifest_store_location.has_value() && has_additional_exclusions) {
tracker.RecordInformational(
InformationalStatusCode::kAssertionDataHashAdditionalExclusionsPresent,
{.url = hard_binding_uri});
}
DataHashBindingHasherTracker data_hash_tracker(&tracker, hard_binding_uri);
auto hasher =
BindingHasher::Create(assertion.alg(), exclusions, data_hash_tracker);
if (hasher == nullptr) {
return;
}
auto digest =
hasher->Digest(contents, data_hash_tracker, start_offset, end_offset);
if (!digest.has_value()) {
return;
}
if (*digest != assertion.hash()) {
tracker.RecordFailure(FailureStatusCode::kAssertionDataHashMismatch,
{.url = hard_binding_uri});
return;
}
tracker.RecordSuccess(SuccessStatusCode::kAssertionDataHashMatch,
{.url = hard_binding_uri});
}
} // namespace credentio