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