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