blob: 8c2aaa83bd0e08237aed00f52430ddc0a663be9c [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 <fstream>
#include <iostream>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <utility>
#include "absl/flags/flag.h"
#include "absl/flags/parse.h"
#include "absl/flags/usage.h"
#include "absl/log/initialize.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "crypto/crypto_read_handler.h"
#include "crypto/default/default_crypto_read_handler.h"
#include "formats/core_registry.h"
#include "google/protobuf/text_format.h"
#include "nlohmann/json_fwd.hpp"
#include "riegeli/bytes/cfile_reader.h"
#include "riegeli/bytes/reader.h"
#include "utils/crjson.h"
#include "utils/media_type.h"
#include "validator/asset_validator_impl.h"
#include "validator/result.h"
#include "validator/validator_options.h"
ABSL_FLAG(std::string, asset, "",
"Path to the asset file to validate (Required).");
ABSL_FLAG(
std::string, claim_signer_trust, "",
"Path to PEM file containing claim signer trust anchors (for C2PA "
"2.2+ strict EKU compliance). If not provided, then the validator will "
"default to skip trust checks.");
ABSL_FLAG(std::string, tsa_trust, "",
"Path to PEM file containing TSA trust anchors. If not provided, "
"then the validator will default to skip TSA trust checks.");
ABSL_FLAG(std::string, output_format, "crjson",
"The format to output the validation results in. [txtpb, crjson] "
"(Default: crjson)");
namespace {
absl::StatusOr<std::string> GetContents(absl::string_view file_path) {
std::ifstream f(std::string(file_path), std::ios::binary);
if (!f.is_open()) {
return absl::NotFoundError(
absl::StrCat("Failed to open file: ", file_path));
}
std::stringstream buffer;
buffer << f.rdbuf();
return buffer.str();
}
absl::Status PrintResultInTxtpb(const credentio::ValidationResult& result) {
std::string text_format;
if (google::protobuf::TextFormat::PrintToString(result.proto(),
&text_format)) {
std::cout << "Validation Result:\n" << text_format << "\n";
} else {
return absl::InternalError("Failed to convert result proto to text format");
}
return absl::OkStatus();
}
absl::Status PrintResultInCrJson(
riegeli::Reader& reader, std::optional<absl::string_view> media_type_opt,
const credentio::ValidationResult& result) {
if (!media_type_opt.has_value()) {
return absl::InvalidArgumentError("Media type is required");
}
auto registry = credentio::CreateCoreFormatRegistry();
auto format = registry->GetFormat(*media_type_opt);
if (!format.ok()) {
return format.status();
}
if (!reader.Seek(0) || reader.pos() != 0) {
return absl::DataLossError("Failed to seek to start of reader");
}
auto manifest_store = (*format)->extractor()->ExtractManifestStore(reader);
if (!manifest_store.ok()) {
return manifest_store.status();
}
absl::StatusOr<nlohmann::json> crjson =
credentio::ConvertToCrJson(*manifest_store, result.proto());
if (!crjson.ok()) {
return crjson.status();
}
std::cout << "Validation Result (crjson):\n" << crjson->dump(2) << "\n";
return absl::OkStatus();
}
} // namespace
int main(int argc, char* argv[]) {
absl::SetProgramUsageMessage(
"Validates C2PA asset files and prints validation results.\n"
"Usage:\n c2pa_validate --asset=<path_to_asset> "
"[--claim_signer_trust=<pem_path>] [--tsa_trust=<pem_path>] "
"[--output_format=<txtpb, crjson>]");
absl::InitializeLog();
absl::ParseCommandLine(argc, argv);
const std::string asset_path = absl::GetFlag(FLAGS_asset);
if (asset_path.empty()) {
std::cerr << "Error: --asset flag is required.\n";
return 1;
}
const std::string output_format = absl::GetFlag(FLAGS_output_format);
if (output_format != "txtpb" && output_format != "crjson") {
std::cerr << "Error: --output_format must be txtpb or crjson.\n";
return 1;
}
const std::string claim_signer_trust_path =
absl::GetFlag(FLAGS_claim_signer_trust);
const std::string tsa_trust_path = absl::GetFlag(FLAGS_tsa_trust);
credentio::DefaultCryptoReadHandlerOptions crypto_options;
std::string claim_signer_trust_pem;
std::string tsa_trust_pem;
if (!claim_signer_trust_path.empty()) {
absl::StatusOr<std::string> pem = GetContents(claim_signer_trust_path);
if (!pem.ok()) {
std::cerr << "Error reading claim signer trust file: " << pem.status()
<< "\n";
return 1;
}
claim_signer_trust_pem = *std::move(pem);
crypto_options.claim_signer_trust_anchors_pem = claim_signer_trust_pem;
} else {
std::cerr << "WARNING: --claim_signer_trust is not provided. Skipping "
"claim signer trust checks.\n";
crypto_options.skip_claim_signer_trust_checks_for_test = true;
}
if (!tsa_trust_path.empty()) {
absl::StatusOr<std::string> pem = GetContents(tsa_trust_path);
if (!pem.ok()) {
std::cerr << "Error reading TSA trust file: " << pem.status() << "\n";
return 1;
}
tsa_trust_pem = *std::move(pem);
crypto_options.tsa_trust_anchors_pem = tsa_trust_pem;
} else {
std::cerr
<< "WARNING: --tsa_trust is not provided. Skipping TSA trust checks.\n";
crypto_options.skip_tsa_trust_checks_for_test = true;
}
absl::StatusOr<std::unique_ptr<credentio::CryptoReadHandler>>
crypto_read_handler =
credentio::CreateDefaultCryptoReadHandler(crypto_options);
if (!crypto_read_handler.ok()) {
std::cerr << "Error creating crypto read handler: "
<< crypto_read_handler.status() << "\n";
return 1;
}
credentio::AssetValidatorImpl validator(credentio::ValidatorOptions{
.crypto_read_handler = *std::move(crypto_read_handler),
});
riegeli::CFileReader<> reader(asset_path);
if (!reader.ok()) {
std::cerr << "Error opening asset file: " << reader.status() << "\n";
return 1;
}
std::optional<absl::string_view> media_type_opt;
const absl::StatusOr<std::string> media_type =
credentio::MediaType(asset_path);
if (media_type.ok()) {
media_type_opt = *media_type;
} else {
std::cerr << "Error determining media type: " << media_type.status()
<< "\n";
}
const absl::StatusOr<std::unique_ptr<credentio::ValidationResult>> result =
validator.Validate(reader, media_type_opt);
if (!result.ok()) {
std::cerr << "Validation failed: " << result.status() << "\n";
return 1;
}
std::cout << "Validation successful!\n";
if (output_format == "crjson") {
absl::Status status = PrintResultInCrJson(reader, media_type_opt, **result);
if (status.ok()) {
return 0;
}
std::cerr << "Failed to print result in crjson, reverting to txtpb: "
<< status << "\n";
}
absl::Status status = PrintResultInTxtpb(**result);
if (!status.ok()) {
std::cerr << "Failed to print result in txtpb: " << status << "\n";
return 1;
}
return 0;
}