blob: 5fd6d9f8b3f2a81880f1ce4b4ab18f99f129c39e [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 "validator/manifest_store_validator_impl.h"
#include <memory>
#include <optional>
#include <utility>
#include <vector>
#include "absl/base/nullability.h"
#include "absl/status/status.h"
#include "absl/status/status_macros.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "assertion/validator.h"
#include "claim/validator.h"
#include "constants/labels.h"
#include "constants/status_codes.h"
#include "cose/verifier.h"
#include "crypto/default/hasher.h"
#include "crypto/hash.h"
#include "jumbf/box.h"
#include "jumbf/parse.h"
#include "jumbf/uri.h"
#include "proto/assertion.pb.h"
#include "proto/ingredient_assertion.pb.h"
#include "proto/validation_result.pb.h"
#include "utils/dual_status_tracker.h"
#include "uuid/uuid.h"
#include "validator/graph.h"
#include "validator/result.h"
#include "validator/validator_metrics.h"
#include "validator/validator_options.h"
namespace credentio {
namespace {
using ::jumbf::SuperBox;
bool IsManifestStore(const SuperBox& superbox) {
return superbox.description.label == kManifestStoreLabel &&
superbox.description.type_uuid == kManifestStoreUuid;
}
bool IsManifest(const SuperBox& superbox) {
const auto& uuid = superbox.description.type_uuid;
return uuid == kStandardManifestUuid || uuid == kUpdateManifestUuid ||
uuid == kCompressedManifestUuid || uuid == kTimestampManifestUuid;
}
absl::StatusOr<std::unique_ptr<PartialValidationResultProto>>
ValidateManifestStore(absl::string_view manifest_store,
const ValidatorOptions& options,
const AssertionValidator& assertion_validator,
const ClaimValidator& claim_validator,
const HashCheckerFactory& hash_checker_factory,
ValidatorMetrics* absl_nullable metrics) {
// Obtain the superbox proto from the serialized manifest.
// Any error from parsing the JUMBF boxes is considered "No Manifest Found"
// A recursion limit of 3 should suffice, but we'll allow for a bit more in
// case of future changes.
absl::StatusOr<SuperBox> ms_superbox_or =
jumbf::ConsumeSuperBox(&manifest_store, /*recursion_limit=*/9);
if (!ms_superbox_or.ok()) {
return absl::NotFoundError(ms_superbox_or.status().message());
}
SuperBox ms_superbox = *std::move(ms_superbox_or);
auto uri_resolver = jumbf::UriResolver::WithSingleRootChild(&ms_superbox);
if (!IsManifestStore(ms_superbox)) {
return absl::NotFoundError(
"JUMBF SuperBox does not contain a manifest store");
}
// Fetch the active manifest from the manifest store.
// The last C2PA Manifest superbox in the C2PA Manifest Store superbox shall
// be considered the active manifest.
std::optional<SuperBox> active_manifest;
for (auto it = ms_superbox.contents.rbegin();
it != ms_superbox.contents.rend(); ++it) {
if (it->Holds<SuperBox>() && IsManifest(it->Get<SuperBox>())) {
active_manifest = it->Get<SuperBox>();
break;
}
}
if (!active_manifest.has_value()) {
return absl::NotFoundError("No active manifest found in manifest store");
}
ManifestGraph graph(&*active_manifest, &uri_resolver, &assertion_validator,
&claim_validator, &hash_checker_factory, &options);
absl::StatusOr<std::unique_ptr<PartialValidationResultProto>> result =
graph.Validate();
if (!result.ok()) {
// Don't suppress the internal error.
if (result.status().code() == absl::StatusCode::kInternal) {
return result;
}
return absl::NotFoundError(result.status().message());
}
return result;
}
} // namespace
ManifestStoreValidatorImpl::ManifestStoreValidatorImpl(ValidatorOptions options)
: cose_verifier_(CreateCoseVerifier({
.crypto_read_handler = std::move(options.crypto_read_handler),
.clock = options.clock,
})),
options_(std::move(options)),
claim_validator_(cose_verifier_.get()),
assertion_validator_({.skip_actions_assertion_validation_for_test =
options_.accept_legacy_manifest_for_test}),
hash_checker_factory_(DefaultHashCheckerFactory()) {}
absl::StatusOr<std::unique_ptr<PartialValidationResultProto>>
ManifestStoreValidatorImpl::Validate(absl::string_view manifest_store) const {
ABSL_ASSIGN_OR_RETURN(
auto partial_validation_result,
ValidateManifestStore(manifest_store, options_, assertion_validator_,
claim_validator_, hash_checker_factory_,
options_.metrics));
if (options_.spec_version >= SpecVersion::kC2pa_2_4) {
partial_validation_result->set_spec_version(
SpecVersionToString(options_.spec_version));
if (!options_.trust_list_uri.empty()) {
partial_validation_result->set_trust_list_uri(options_.trust_list_uri);
}
}
if (options_.metrics != nullptr) {
options_.metrics->RecordPartialValidationResult(*partial_validation_result);
}
if (partial_validation_result->has_hard_binding_uri()) {
// The manifest store indicates that a hard binding assertion is present.
const Assertion* absl_nullable assertion =
GetAssertion(partial_validation_result.get(),
partial_validation_result->hard_binding_uri());
if (assertion == nullptr) {
// The assertion cannot be found, this means it was redacted for being a
// deprecated assertion.
ABSL_ASSIGN_OR_RETURN(auto tracker,
DualStatusTracker::FromPartialValidationResult(
partial_validation_result.get()));
tracker->RecordFailure(
FailureStatusCode::kClaimHardBindingsMissing,
{.explanation = "The active hard binding assertion was deprecated."});
partial_validation_result->clear_hard_binding_uri();
}
}
return partial_validation_result;
}
} // namespace credentio