| // 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 |