| // 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 <sstream> | |
| #include <string> | |
| #include <utility> | |
| #include <vector> | |
| #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/default/pem.h" | |
| #include "google/protobuf/text_format.h" | |
| #include "proto/generator.pb.h" | |
| #include "testing/local_key_crypto_write_handler.h" | |
| #include "tools/asset_writer.h" | |
| ABSL_FLAG(std::string, input, "", | |
| "Path to the input asset file to read from (Required)."); | |
| ABSL_FLAG(std::string, output, "", | |
| "Path to the output asset file to write to (Required)."); | |
| ABSL_FLAG(std::string, claim_signer_key, "", | |
| "Path to PEM file containing the claim signer's EC P-256 private key " | |
| "(Required)."); | |
| ABSL_FLAG(std::string, claim_signer_cert, "", | |
| "Path to PEM file containing the claim signer's certificate chain, " | |
| "leaf first (Required)."); | |
| ABSL_FLAG(std::string, claim_signer_ocsp, "", | |
| "Path to DER file containing an OCSP response for the claim signer " | |
| "certificate (Optional)."); | |
| ABSL_FLAG(std::string, tsa_key, "", | |
| "Path to PEM file containing the TSA's EC P-256 private key " | |
| "(Optional; required unless timestamping is disabled)."); | |
| ABSL_FLAG(std::string, tsa_cert, "", | |
| "Path to PEM file containing the TSA's certificate " | |
| "(Optional; required unless timestamping is disabled)."); | |
| ABSL_FLAG(bool, disable_timestamping, false, | |
| "If true, disables timestamping when writing the C2PA manifest store " | |
| "(Default: false)."); | |
| ABSL_FLAG(std::string, request_txtpb, "", | |
| "Path to textproto file containing GenerateManifestStoreRequest " | |
| "(Optional; defaults to a basic 'created' action)."); | |
| 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(); | |
| } | |
| } // namespace | |
| int main(int argc, char* argv[]) { | |
| absl::SetProgramUsageMessage( | |
| "Writes C2PA manifest stores into asset files.\n" | |
| "Usage:\n asset_write --input=<input_path> --output=<output_path> " | |
| "--claim_signer_key=<pem_path> --claim_signer_cert=<pem_path> " | |
| "[--claim_signer_ocsp=<der_path>] [--tsa_key=<pem_path>] " | |
| "[--tsa_cert=<pem_path>] [--disable_timestamping] " | |
| "[--request_txtpb=<txtpb_path>]"); | |
| absl::InitializeLog(); | |
| absl::ParseCommandLine(argc, argv); | |
| const std::string input_path = absl::GetFlag(FLAGS_input); | |
| if (input_path.empty()) { | |
| std::cerr << "Error: --input flag is required.\n"; | |
| return 1; | |
| } | |
| const std::string output_path = absl::GetFlag(FLAGS_output); | |
| if (output_path.empty()) { | |
| std::cerr << "Error: --output flag is required.\n"; | |
| return 1; | |
| } | |
| const std::string claim_signer_key_path = | |
| absl::GetFlag(FLAGS_claim_signer_key); | |
| if (claim_signer_key_path.empty()) { | |
| std::cerr << "Error: --claim_signer_key flag is required.\n"; | |
| return 1; | |
| } | |
| const std::string claim_signer_cert_path = | |
| absl::GetFlag(FLAGS_claim_signer_cert); | |
| if (claim_signer_cert_path.empty()) { | |
| std::cerr << "Error: --claim_signer_cert flag is required.\n"; | |
| return 1; | |
| } | |
| credentio::LocalKeyCryptoWriteHandlerOptions crypto_options; | |
| absl::StatusOr<std::string> key_pem = GetContents(claim_signer_key_path); | |
| if (!key_pem.ok()) { | |
| std::cerr << "Error reading claim signer key file: " << key_pem.status() | |
| << "\n"; | |
| return 1; | |
| } | |
| crypto_options.claim_signer.key_pem = *std::move(key_pem); | |
| absl::StatusOr<std::string> cert_pem = GetContents(claim_signer_cert_path); | |
| if (!cert_pem.ok()) { | |
| std::cerr << "Error reading claim signer cert file: " << cert_pem.status() | |
| << "\n"; | |
| return 1; | |
| } | |
| absl::StatusOr<std::vector<std::string>> cert_chain = | |
| credentio::LoadCertsFromPem(*cert_pem); | |
| if (!cert_chain.ok()) { | |
| std::cerr << "Error parsing claim signer certificate chain: " | |
| << cert_chain.status() << "\n"; | |
| return 1; | |
| } | |
| crypto_options.claim_signer.certificate_chain = *std::move(cert_chain); | |
| const std::string claim_signer_ocsp_path = | |
| absl::GetFlag(FLAGS_claim_signer_ocsp); | |
| if (!claim_signer_ocsp_path.empty()) { | |
| absl::StatusOr<std::string> ocsp_der = GetContents(claim_signer_ocsp_path); | |
| if (!ocsp_der.ok()) { | |
| std::cerr << "Error reading claim signer OCSP file: " << ocsp_der.status() | |
| << "\n"; | |
| return 1; | |
| } | |
| crypto_options.claim_signer.ocsp_responses.push_back(*std::move(ocsp_der)); | |
| } | |
| const std::string tsa_key_path = absl::GetFlag(FLAGS_tsa_key); | |
| const std::string tsa_cert_path = absl::GetFlag(FLAGS_tsa_cert); | |
| bool disable_timestamping = absl::GetFlag(FLAGS_disable_timestamping); | |
| if (!disable_timestamping && tsa_key_path.empty() && tsa_cert_path.empty()) { | |
| std::cerr << "WARNING: --tsa_key and --tsa_cert are not provided. " | |
| "Disabling timestamping.\n"; | |
| disable_timestamping = true; | |
| } | |
| crypto_options.disable_timestamping = disable_timestamping; | |
| if (!disable_timestamping) { | |
| if (tsa_key_path.empty() || tsa_cert_path.empty()) { | |
| std::cerr << "Error: Both --tsa_key and --tsa_cert must be provided when " | |
| "timestamping is enabled.\n"; | |
| return 1; | |
| } | |
| absl::StatusOr<std::string> tsa_key_pem = GetContents(tsa_key_path); | |
| if (!tsa_key_pem.ok()) { | |
| std::cerr << "Error reading TSA key file: " << tsa_key_pem.status() | |
| << "\n"; | |
| return 1; | |
| } | |
| crypto_options.tsa.key_pem = *std::move(tsa_key_pem); | |
| absl::StatusOr<std::string> tsa_cert_pem = GetContents(tsa_cert_path); | |
| if (!tsa_cert_pem.ok()) { | |
| std::cerr << "Error reading TSA cert file: " << tsa_cert_pem.status() | |
| << "\n"; | |
| return 1; | |
| } | |
| crypto_options.tsa.cert_pem = *std::move(tsa_cert_pem); | |
| } | |
| credentio::GenerateManifestStoreRequest request; | |
| const std::string request_txtpb_path = absl::GetFlag(FLAGS_request_txtpb); | |
| if (!request_txtpb_path.empty()) { | |
| absl::StatusOr<std::string> request_txtpb = GetContents(request_txtpb_path); | |
| if (!request_txtpb.ok()) { | |
| std::cerr << "Error reading request txtpb file: " | |
| << request_txtpb.status() << "\n"; | |
| return 1; | |
| } | |
| if (!google::protobuf::TextFormat::ParseFromString(*request_txtpb, | |
| &request)) { | |
| std::cerr << "Error parsing GenerateManifestStoreRequest from txtpb.\n"; | |
| return 1; | |
| } | |
| } else { | |
| request.mutable_actions()->mutable_created(); | |
| } | |
| absl::Status status = credentio::WriteAssetFile( | |
| std::move(crypto_options), std::move(request), input_path, output_path); | |
| if (!status.ok()) { | |
| std::cerr << "Asset write failed: " << status << "\n"; | |
| return 1; | |
| } | |
| std::cout << "Successfully wrote C2PA asset to " << output_path << "\n"; | |
| return 0; | |
| } |