| // 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 <ostream> | |
| #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/status_macros.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 "google/protobuf/text_format.h" | |
| #include "nlohmann/json_fwd.hpp" | |
| #include "riegeli/bytes/cfile_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)"); | |
| ABSL_FLAG(std::string, output_file, "", | |
| "Path to the output file to write validation results to (Optional; " | |
| "defaults to stdout)."); | |
| 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::ostream& out) { | |
| std::string text_format; | |
| if (google::protobuf::TextFormat::PrintToString(result.proto(), | |
| &text_format)) { | |
| out << text_format; | |
| } else { | |
| return absl::InternalError("Failed to convert result proto to text format"); | |
| } | |
| return absl::OkStatus(); | |
| } | |
| absl::Status PrintResultInCrJson(const credentio::ValidationResult& result, | |
| std::ostream& out) { | |
| ABSL_ASSIGN_OR_RETURN(nlohmann::json crjson, | |
| credentio::ConvertToCrJson(result)); | |
| out << crjson.dump(2); | |
| 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>] [--output_file=<output_path>]"); | |
| 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; | |
| } | |
| const std::string output_path = absl::GetFlag(FLAGS_output_file); | |
| std::ofstream file_out; | |
| std::ostream* out = &std::cout; | |
| if (!output_path.empty()) { | |
| file_out.open(output_path); | |
| if (!file_out.is_open()) { | |
| std::cerr << "Error: Failed to open output file: " << output_path << "\n"; | |
| return 1; | |
| } | |
| out = &file_out; | |
| } | |
| if (output_format == "crjson") { | |
| absl::Status status = PrintResultInCrJson(**result, *out); | |
| if (!status.ok()) { | |
| std::cerr << "Failed to print result in crjson: " << status << "\n"; | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| absl::Status status = PrintResultInTxtpb(**result, *out); | |
| if (!status.ok()) { | |
| std::cerr << "Failed to print result in txtpb: " << status << "\n"; | |
| return 1; | |
| } | |
| return 0; | |
| } |