blob: bb68c5974ff1173b05d28f2dbe524d5d086dae56 [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 "assertion/references_validator.h"
#include <string>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "assertion/assertion_parser.h"
#include "assertion/hashed_uri_validator.h"
#include "constants/labels.h"
#include "constants/status_codes.h"
#include "proto/actions_assertion.pb.h"
#include "proto/assertion.pb.h"
#include "proto/hashed_uri.pb.h"
#include "proto/ingredient_assertion.pb.h"
#include "validator/tracker.h"
namespace credentio {
namespace {
bool ValidateHashedUri(const HashedUri& hashed_uri,
const HashedUriValidator& hashed_uri_validator,
absl::string_view assertion_url,
ValidationTracker& validation_tracker) {
return hashed_uri_validator
.Validate(hashed_uri, assertion_url, validation_tracker)
.has_value();
}
bool IsUnsupportedRelatedAssertionLabel(absl::string_view label) {
return label == kIngredientAssertionV1Label ||
label == kIngredientAssertionV2Label ||
label == kIngredientAssertionV3Label ||
label == kActionsAssertionV1Label || label == kActionsAssertionV2Label;
}
bool ValidateTypeForRelatedAssertion(absl::string_view related_path,
absl::string_view manifest_label,
absl::string_view assertion_url,
ValidationTracker& validation_tracker) {
const std::vector<absl::string_view> related_tokens =
absl::StrSplit(related_path, '/');
if (related_tokens.size() <= 3) {
return false;
}
if (related_tokens[2] != manifest_label) {
validation_tracker.RecordFailure(
FailureStatusCode::kAssertionActionMalformed,
{.url = assertion_url,
.explanation = "Related Assertion is not in the same manifest as the "
"action assertion."});
return false;
}
const absl::string_view label =
StripMultipleInstanceSuffix(related_tokens.back());
if (IsUnsupportedRelatedAssertionLabel(label)) {
validation_tracker.RecordFailure(
FailureStatusCode::kAssertionActionMalformed,
{.url = assertion_url,
.explanation =
absl::StrCat("unsupported related assertion type: ", label)});
return false;
}
return true;
}
bool ValidateRelatedAssertions(const ActionParameters& parameters,
const HashedUriValidator& hashed_uri_validator,
absl::string_view manifest_label,
absl::string_view assertion_url,
ValidationTracker& validation_tracker) {
for (const auto& related_uri : parameters.related_assertions()) {
auto path = hashed_uri_validator.Validate(related_uri, assertion_url,
validation_tracker);
if (!path.has_value()) {
// Note: C2PA 2.4 Validation
// (https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html#_c2pa_actions_validation)
// states that the HashedUri should be validated (which could add failure
// codes), and then states to additionally add the failure code
// assertion.action.malformed if the referenced assertion can not be
// resolved (i.e. is missing or hash mismatch) this results in two
// failures for the same issue.
validation_tracker.RecordFailure(
FailureStatusCode::kAssertionActionMalformed,
{.url = assertion_url,
.explanation = absl::StrCat("could not resolve related assertion: ",
related_uri.url())});
return false;
}
if (!ValidateTypeForRelatedAssertion(*path, manifest_label, assertion_url,
validation_tracker)) {
return false;
}
}
return true;
}
template <typename ActionsT>
bool ValidateActions(const ActionsT& actions,
const HashedUriValidator& hashed_uri_validator,
absl::string_view manifest_label,
absl::string_view assertion_url,
ValidationTracker& validation_tracker) {
for (const auto& action : actions.actions()) {
if (!action.has_parameters()) {
continue;
}
for (const auto& ingredient : action.parameters().ingredients()) {
if (!ValidateHashedUri(ingredient, hashed_uri_validator, assertion_url,
validation_tracker)) {
return false;
}
}
if (!ValidateRelatedAssertions(action.parameters(), hashed_uri_validator,
manifest_label, assertion_url,
validation_tracker)) {
return false;
}
}
return true;
}
bool ValidateIngredientV3(const IngredientAssertionV3& ingredient,
const HashedUriValidator& hashed_uri_validator,
absl::string_view assertion_url,
ValidationTracker& validation_tracker) {
// `active_manifest` and `claim_signature` are validated in `ManifestGraph`
// when appropriate -- the `active_manifest` hash won't validate when the
// ingredient has redactions.
return !ingredient.has_thumbnail() ||
ValidateHashedUri(ingredient.thumbnail(), hashed_uri_validator,
assertion_url, validation_tracker);
}
} // namespace
bool ReferencesValidator::Validate(const Assertion& assertion) const {
const std::string assertion_url = absl::StrCat(
kAssertionStoreLabel, kManifestLabelDelimiter, assertion.label());
switch (assertion.assertion_case()) {
case Assertion::kActionsV1:
return ValidateActions(assertion.actions_v1(), hashed_uri_validator_,
manifest_label_, assertion_url, tracker_);
case Assertion::kActions:
return ValidateActions(assertion.actions(), hashed_uri_validator_,
manifest_label_, assertion_url, tracker_);
case Assertion::kIngredientV3:
return ValidateIngredientV3(assertion.ingredient_v3(),
hashed_uri_validator_, assertion_url,
tracker_);
default:
return true;
}
}
} // namespace credentio