blob: 9785e52823a6fa99d88108e838d1e1a392cd1ba6 [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 <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;
}