blob: 1b2c24c04ed1f58dc95e20da237807d3e00b6136 [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 "utils/crjson.h"
#include <cstddef>
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "absl/base/nullability.h"
#include "absl/container/flat_hash_map.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/match.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "cbor/cbor.h"
#include "cbor/parse.h"
#include "constants/labels.h"
#include "cose/sig_structure.h"
#include "cose/simple_cms_parser.h"
#include "crypto/algorithms.h"
#include "google/protobuf/repeated_ptr_field.h"
#include "jumbf/box.h"
#include "jumbf/convert_json.h" // IWYU pragma: keep
#include "jumbf/parse.h"
#include "nlohmann/json.hpp"
#include "nlohmann/json_fwd.hpp"
#include "openssl/asn1.h"
#include "openssl/bn.h"
#include "openssl/mem.h"
#include "openssl/obj.h"
#include "openssl/x509.h"
#include "proto/ingredient_assertion.pb.h"
#include "proto/ingredient_validation_result.pb.h"
#include "proto/manifest.pb.h"
#include "proto/validation_result.pb.h"
#include "proto/validation_status.pb.h"
#include "tsp/timestamp_verifier.h"
#include "utils/crjson_utils.h"
namespace credentio {
namespace {
using Json = ::nlohmann::json;
// =============================================================================
// JUMBF/CBOR Helpers
// =============================================================================
template <typename T>
absl::StatusOr<const T&> EnsureAndGetSingleBox(const jumbf::SuperBox& box) {
if (box.contents.size() != 1) {
return absl::InvalidArgumentError(
absl::StrCat(box.description.label.value_or("unspecified"),
" box must contain exactly one box"));
}
if (!box.contents[0].Holds<T>()) {
return absl::InvalidArgumentError(absl::StrCat(
box.description.label.value_or("unspecified"),
" must contained box of index: ", box.contents[0].payload.index()));
}
return box.contents[0].Get<T>();
}
template <typename T>
struct ParsedCbor {
std::unique_ptr<cbor::ParseResult> parse_result;
T cbor_view;
};
absl::StatusOr<ParsedCbor<cbor::MapView>> ParseCborMap(absl::string_view cbor) {
auto parse_result = cbor::Parse(cbor);
if (!parse_result.ok()) {
return parse_result.status();
}
auto cbor_map = (*parse_result)->AsMap();
if (!cbor_map.ok()) {
return cbor_map.status();
}
return ParsedCbor<cbor::MapView>{std::move(*parse_result), *cbor_map};
}
absl::StatusOr<ParsedCbor<cbor::MapView>> GetCborMap(
const jumbf::SuperBox& box) {
auto cbor_box = EnsureAndGetSingleBox<jumbf::CborBox>(box);
if (!cbor_box.ok()) {
return cbor_box.status();
}
return ParseCborMap(cbor_box->payload);
}
enum class ManifestType {
kUnknown,
kStandard,
kUpdate,
kCompressed,
kTimestamp,
};
ManifestType GetManifestType(const jumbf::SuperBox& superbox) {
const auto& uuid = superbox.description.type_uuid;
if (uuid == kStandardManifestUuid) {
return ManifestType::kStandard;
}
if (uuid == kUpdateManifestUuid) {
return ManifestType::kUpdate;
}
if (uuid == kCompressedManifestUuid) {
return ManifestType::kCompressed;
}
if (uuid == kTimestampManifestUuid) {
return ManifestType::kTimestamp;
}
return ManifestType::kUnknown;
}
// =============================================================================
// Generic Array Converters
// =============================================================================
Json ConvertArrayOfStrings(uint32_t _, const cbor::ItemView& item) {
auto val = item.GetString();
if (!val.ok()) {
return Json({{"_error", val.status().message()}});
}
return Json(*val);
}
// =============================================================================
// Assertion Converters
// =============================================================================
Json ConvertThumbnailAssertion(const jumbf::SuperBox& box) {
Json crjson = Json::object();
absl::string_view label = box.description.label.value_or("");
if (absl::StartsWith(label, "c2pa.thumbnail.claim")) {
crjson["thumbnailType"] = 0;
} else if (absl::StartsWith(label, "c2pa.thumbnail.ingredient")) {
crjson["thumbnailType"] = 1;
}
size_t prefix_len = absl::StartsWith(label, "c2pa.thumbnail.claim") ? 20 : 25;
size_t last_dot = label.rfind('.');
if (last_dot != absl::string_view::npos && last_dot >= prefix_len) {
absl::string_view mime_type = label.substr(last_dot + 1);
size_t suffix_start = mime_type.find("__");
if (suffix_start != absl::string_view::npos) {
mime_type = mime_type.substr(0, suffix_start);
}
crjson["mimeType"] = std::string(mime_type);
}
return crjson;
}
Json ConvertMetadataAssertion(const jumbf::SuperBox& box) {
Json crjson = Json::object();
auto json_box = EnsureAndGetSingleBox<jumbf::JsonBox>(box);
if (json_box.ok()) {
Json j;
jumbf::to_json(j, *json_box);
if (j.contains("json")) {
return j["json"];
} else {
crjson["_error"] = "Failed to convert JSON box to JSON";
return crjson;
}
} else {
crjson["_error"] = "Metadata assertion is not a JSON box";
return crjson;
}
}
bool LabelMatches(absl::string_view label, absl::string_view base_label) {
if (!absl::StartsWith(label, base_label)) {
return false;
}
if (label.size() == base_label.size()) {
return true;
}
if (label.size() > base_label.size() + 2 &&
label.substr(base_label.size(), 2) == "__") {
absl::string_view number = label.substr(base_label.size() + 2);
uint64_t val;
return absl::SimpleAtoi(number, &val);
}
return false;
}
// This is an allowlist of standard assertions that are supported by C2PA v2.4.
bool IsAllowlistedStandardAssertion(absl::string_view label) {
return LabelMatches(label, kDataHashAssertionLabel) ||
LabelMatches(label, kDataHashAssertionPartLabel) ||
LabelMatches(label, kBmffBasedHashAssertionV2Label) ||
LabelMatches(label, kBmffBasedHashAssertionV3Label) ||
LabelMatches(label, kBmffBasedHashAssertionV2PartLabel) ||
LabelMatches(label, kBmffBasedHashAssertionV3PartLabel) ||
LabelMatches(label, kBoxesHashAssertionLabel) ||
LabelMatches(label, kBoxesHashAssertionPartLabel) ||
LabelMatches(label, kMultiAssetHashAssertionLabel) ||
LabelMatches(label, kCollectionDataHashAssertionLabel) ||
LabelMatches(label, kSoftBindingAssertionLabel) ||
LabelMatches(label, kActionsAssertionV1Label) ||
LabelMatches(label, kActionsAssertionV2Label) ||
LabelMatches(label, kIngredientAssertionV1Label) ||
LabelMatches(label, kIngredientAssertionV2Label) ||
LabelMatches(label, kIngredientAssertionV3Label) ||
LabelMatches(label, "font.info") ||
LabelMatches(label, "c2pa.font.info") ||
LabelMatches(label, "c2pa.cloud-data") ||
LabelMatches(label, "c2pa.session-keys") ||
LabelMatches(label, "c2pa.ai-disclosure") ||
LabelMatches(label, "c2pa.certificate-status") ||
LabelMatches(label, "c2pa.asset-ref") ||
LabelMatches(label, "c2pa.depthmap") ||
LabelMatches(label, "c2pa.repository-receipt") ||
LabelMatches(label, "c2pa.embedded_data") ||
LabelMatches(label, "c2pa.environmental-sustainability") ||
LabelMatches(label, "c2pa.time-stamp");
}
Json ConvertAssertionStore(const jumbf::SuperBox& box) {
Json crjson = Json::object();
for (const jumbf::ContentBox& content_box : box.contents) {
if (!content_box.Holds<jumbf::SuperBox>()) {
crjson["_error"] = "Assertion store contains non-SuperBox content";
continue;
}
const jumbf::SuperBox& child_box = content_box.Get<jumbf::SuperBox>();
absl::string_view label = child_box.description.label.value_or("");
if (absl::StartsWith(label, "c2pa.thumbnail.claim") ||
absl::StartsWith(label, "c2pa.thumbnail.ingredient")) {
crjson[label] = ConvertThumbnailAssertion(child_box);
} else if (LabelMatches(label, "c2pa.metadata")) {
crjson[label] = ConvertMetadataAssertion(child_box);
} else if (IsAllowlistedStandardAssertion(label)) {
if (auto cbor_box = EnsureAndGetSingleBox<jumbf::CborBox>(child_box);
cbor_box.ok()) {
Json j;
jumbf::to_json(j, *cbor_box);
if (j.contains("cbor")) {
crjson[label] = j["cbor"];
} else {
crjson[label] = Json::object();
crjson[label]["_error"] = "Failed to convert CBOR to JSON";
}
} else if (auto json_box =
EnsureAndGetSingleBox<jumbf::JsonBox>(child_box);
json_box.ok()) {
Json j;
jumbf::to_json(j, *json_box);
if (j.contains("json")) {
crjson[label] = j["json"];
} else {
crjson[label] = Json::object();
crjson[label]["_error"] = "Failed to convert JSON box to JSON";
}
}
} else {
crjson[label] = Json::object();
}
}
return crjson;
}
// =============================================================================
// Claim Helpers
// =============================================================================
// Converts a hashed URI map to a crJSON object.
// - CBOR CDDL:
// https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html#_embedded
Json ConvertHashedUri(const cbor::MapView& uri_map) {
Json crjson = Json::object();
RecordString(uri_map, "url", true, crjson);
RecordString(uri_map, "alg", false, crjson);
RecordByteString(uri_map, "hash", true, crjson);
return crjson;
}
Json ConvertClaimGeneratorInfo(const cbor::MapView& map) {
Json crjson = Json::object();
RecordString(map, "name", true, crjson);
RecordString(map, "version", false, crjson);
RecordMap(map, "icon", false, ConvertHashedUri, crjson);
RecordString(map, "operating_system", false, crjson);
return crjson;
}
Json ConvertRating(const cbor::MapView& map) {
Json rating = Json::object();
RecordString(map, "value", true, rating);
RecordString(map, "code", false, rating);
RecordString(map, "explanation", false, rating);
return rating;
}
Json ConvertSourceMap(const cbor::MapView& map) {
Json crjson = Json::object();
RecordString(map, "type", true, crjson);
RecordString(map, "details", false, crjson);
return crjson;
}
Json ConvertLocalizations(const cbor::MapView& map) {
Json crjson = Json::object();
// Dynamic fields...
return crjson;
}
Json ConvertRegionOfInterest(const cbor::MapView& map) {
Json crjson = Json::object();
// No fields in the spec.
return crjson;
}
Json ConvertMetadata(const cbor::MapView& map) {
Json crjson = Json::object();
RecordString(map, "dateTime", false, crjson);
RecordArrayOfMaps(map, "rating", false, ConvertRating, crjson);
RecordMap(map, "reference", false, ConvertHashedUri, crjson);
RecordMap(map, "dataSource", false, ConvertSourceMap, crjson);
RecordArrayOfMaps(map, "localizations", false, ConvertLocalizations, crjson);
RecordMap(map, "regionOfInterest", false, ConvertRegionOfInterest, crjson);
return crjson;
}
// =============================================================================
// Claim Converters
// =============================================================================
Json ConvertClaimV2(const jumbf::SuperBox& box) {
Json crjson = Json::object();
auto parsed_cbor = GetCborMap(box);
if (!parsed_cbor.ok()) {
crjson["_error"] = parsed_cbor.status().message();
return crjson;
}
RecordString(parsed_cbor->cbor_view, "instanceID", true, crjson);
RecordMap(parsed_cbor->cbor_view, "claim_generator_info", true,
ConvertClaimGeneratorInfo, crjson);
RecordString(parsed_cbor->cbor_view, "signature", true, crjson);
RecordArrayOfMaps(parsed_cbor->cbor_view, "created_assertions", true,
ConvertHashedUri, crjson);
RecordArrayOfMaps(parsed_cbor->cbor_view, "gathered_assertions", true,
ConvertHashedUri, crjson);
RecordString(parsed_cbor->cbor_view, "dc:title", false, crjson);
RecordArray(parsed_cbor->cbor_view, "redacted_assertions", true,
ConvertArrayOfStrings, crjson);
RecordString(parsed_cbor->cbor_view, "alg", false, crjson);
RecordString(parsed_cbor->cbor_view, "alg_soft", false, crjson);
RecordString(parsed_cbor->cbor_view, "specVersion", false, crjson);
RecordMap(parsed_cbor->cbor_view, "metadata", false, ConvertMetadata, crjson);
return crjson;
}
// =============================================================================
// Signature Helpers
// =============================================================================
std::string SigningAlgorithmToString(SigningAlgorithm alg) {
switch (alg) {
case SigningAlgorithm::kEs256:
return "ES256";
case SigningAlgorithm::kEs384:
return "ES384";
case SigningAlgorithm::kEs512:
return "ES512";
case SigningAlgorithm::kPs256:
return "PS256";
case SigningAlgorithm::kPs384:
return "PS384";
case SigningAlgorithm::kPs512:
return "PS512";
case SigningAlgorithm::kEdDsa:
return "Ed25519";
}
return "Unknown";
}
Json ConvertDNParsed(X509_NAME* name) {
Json dn_map = Json::object();
if (!name) return dn_map;
int count = X509_NAME_entry_count(name);
for (int i = 0; i < count; ++i) {
X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, i);
ASN1_OBJECT* obj = X509_NAME_ENTRY_get_object(entry);
ASN1_STRING* str = X509_NAME_ENTRY_get_data(entry);
int nid = OBJ_obj2nid(obj);
const char* short_name = OBJ_nid2sn(nid);
unsigned char* utf8_str = nullptr;
int len = ASN1_STRING_to_UTF8(&utf8_str, str);
if (len >= 0) {
if (short_name) {
dn_map[short_name] =
std::string(reinterpret_cast<char*>(utf8_str), len);
} else {
char oid_buf[128];
OBJ_obj2txt(oid_buf, sizeof(oid_buf), obj, 1);
dn_map[oid_buf] = std::string(reinterpret_cast<char*>(utf8_str), len);
}
OPENSSL_free(utf8_str);
}
}
return dn_map;
}
std::string ConvertASN1TimeToISO8601(const ASN1_TIME* t) {
if (!t) return "";
int64_t posix_time;
if (ASN1_TIME_to_posix(t, &posix_time) == 0) {
return "";
}
return absl::FormatTime(absl::RFC3339_full, absl::FromUnixSeconds(posix_time),
absl::UTCTimeZone());
}
Json ConvertCertificateInfo(absl::string_view certificate_der) {
Json cert_info = Json::object();
const uint8_t* p = reinterpret_cast<const uint8_t*>(certificate_der.data());
X509* cert = d2i_X509(nullptr, &p, certificate_der.size());
if (!cert) {
cert_info["_error"] = "Failed to parse certificate";
return cert_info;
}
ASN1_INTEGER* serial = X509_get_serialNumber(cert);
BIGNUM* bn = ASN1_INTEGER_to_BN(serial, nullptr);
if (bn) {
char* hex = BN_bn2hex(bn);
if (hex) {
cert_info["serialNumber"] = std::string(hex);
OPENSSL_free(hex);
}
BN_free(bn);
}
cert_info["subject"] = ConvertDNParsed(X509_get_subject_name(cert));
cert_info["issuer"] = ConvertDNParsed(X509_get_issuer_name(cert));
Json validity = Json::object();
validity["notBefore"] = ConvertASN1TimeToISO8601(X509_get0_notBefore(cert));
validity["notAfter"] = ConvertASN1TimeToISO8601(X509_get0_notAfter(cert));
cert_info["validity"] = validity;
X509_free(cert);
return cert_info;
}
// =============================================================================
// Signature Converter
// =============================================================================
Json ConvertClaimSignature(const jumbf::SuperBox& box) {
Json crjson = Json::object();
auto cbor_box = EnsureAndGetSingleBox<jumbf::CborBox>(box);
if (!cbor_box.ok()) {
return crjson;
}
auto cose_sign1 = DecodeCoseSign1TaggedStructure(cbor_box->payload);
if (!cose_sign1.ok()) {
return crjson;
}
auto protected_header = DecodeProtectedHeader(cose_sign1->protected_header);
if (protected_header.ok()) {
crjson["algorithm"] = SigningAlgorithmToString(protected_header->alg);
}
std::vector<std::string> cert_chain;
if (!cose_sign1->unprotected_header.certificate_chain.empty()) {
cert_chain = cose_sign1->unprotected_header.certificate_chain;
} else if (protected_header.ok()) {
cert_chain = protected_header->certificate_chain;
}
if (!cert_chain.empty()) {
crjson["certificateInfo"] = ConvertCertificateInfo(cert_chain[0]);
}
// Handle Timestamp
auto timestamp_container = cose_sign1->unprotected_header.sig_tst2;
if (!timestamp_container.has_value()) {
timestamp_container = cose_sign1->unprotected_header.sig_tst;
}
if (timestamp_container.has_value() &&
!timestamp_container->tst_tokens.empty()) {
if (timestamp_container->tst_tokens.size() == 1) {
absl::string_view timestamp_token =
timestamp_container->tst_tokens[0].val;
TimestampVerifier verifier(nullptr); // Skip trust checks
auto parsed_token = SimpleParsedTimestampToken::Create(timestamp_token);
if (parsed_token.ok()) {
auto verified_timestamp = verifier.VerifyTimestampToken(**parsed_token);
if (verified_timestamp.ok()) {
Json ts_info = Json::object();
ts_info["timestamp"] = absl::FormatTime(
absl::RFC3339_full, verified_timestamp->asserted_time(),
absl::UTCTimeZone());
ts_info["certificateInfo"] =
ConvertCertificateInfo(verified_timestamp->tsa_certificate());
crjson["timeStampInfo"] = ts_info;
}
}
}
}
return crjson;
}
Json ConvertValidationStatusSetToJson(
const ValidationStatusSet& validation,
std::optional<absl::string_view> spec_version = std::nullopt,
std::optional<absl::string_view> trust_list_uri = std::nullopt) {
Json v = Json::object();
v["success"] = Json::array();
v["informational"] = Json::array();
v["failure"] = Json::array();
if (spec_version.has_value()) {
v["specVersion"] = *spec_version;
}
if (trust_list_uri.has_value()) {
v["trustListURI"] = *trust_list_uri;
}
v["validationTime"] =
absl::FormatTime(absl::RFC3339_full, absl::Now(), absl::UTCTimeZone());
for (const auto& success : validation.successes()) {
Json s = Json::object();
s["code"] = success.code();
if (!success.url().empty()) {
s["url"] = success.url();
}
if (!success.explanation().empty()) {
s["explanation"] = success.explanation();
}
v["success"].push_back(s);
}
for (const auto& informational : validation.informationals()) {
Json i = Json::object();
i["code"] = informational.code();
if (!informational.url().empty()) {
i["url"] = informational.url();
}
if (!informational.explanation().empty()) {
i["explanation"] = informational.explanation();
}
v["informational"].push_back(i);
}
for (const auto& failure : validation.failures()) {
Json f = Json::object();
f["code"] = failure.code();
if (!failure.url().empty()) {
f["url"] = failure.url();
}
if (!failure.explanation().empty()) {
f["explanation"] = failure.explanation();
}
v["failure"].push_back(f);
}
return v;
}
// =============================================================================
// Top-Level Converters
// =============================================================================
Json ConvertManifestToCrJson(
const jumbf::SuperBox& manifest_box,
const ValidationStatusSet* validation_status = nullptr,
const google::protobuf::RepeatedPtrField<IngredientDeltaValidationResult>*
ingredient_deltas = nullptr,
std::optional<absl::string_view> spec_version = std::nullopt,
std::optional<absl::string_view> trust_list_uri = std::nullopt) {
Json crjson = Json::object();
crjson["label"] = manifest_box.description.label.value_or("");
switch (GetManifestType(manifest_box)) {
case ManifestType::kStandard:
crjson["isUpdateManifest"] = false;
crjson["isCompressedManifest"] = false;
break;
case ManifestType::kUpdate:
crjson["isUpdateManifest"] = true;
crjson["isCompressedManifest"] = false;
break;
case ManifestType::kCompressed:
crjson["isCompressedManifest"] = true;
crjson["_error"] = "Compressed manifests are not supported";
// Compressed manifest are not supported, so mark an error and return.
return crjson;
default:
crjson["_error"] = "Unknown manifest type";
// Unknown manifest type, so mark an error and return.
return crjson;
}
crjson["assertions"] = Json::object();
crjson["signature"] = Json::object();
if (validation_status != nullptr) {
crjson["validationResults"] = ConvertValidationStatusSetToJson(
*validation_status, spec_version, trust_list_uri);
} else {
crjson["validationResults"] = Json::object();
crjson["validationResults"]["success"] = Json::array();
crjson["validationResults"]["informational"] = Json::array();
crjson["validationResults"]["failure"] = Json::array();
if (spec_version.has_value()) {
crjson["validationResults"]["specVersion"] = *spec_version;
}
if (trust_list_uri.has_value()) {
crjson["validationResults"]["trustListURI"] = *trust_list_uri;
}
crjson["validationResults"]["validationTime"] =
absl::FormatTime(absl::RFC3339_full, absl::Now(), absl::UTCTimeZone());
}
if (ingredient_deltas != nullptr && !ingredient_deltas->empty()) {
crjson["ingredientDeltas"] = Json::array();
for (const auto& delta : *ingredient_deltas) {
Json d = Json::object();
d["ingredientAssertionURI"] = delta.ingredient_assertion_uri();
// Ingredient deltas should not have spec version or trust list URI.
d["validationDeltas"] =
ConvertValidationStatusSetToJson(delta.validation_deltas());
crjson["ingredientDeltas"].push_back(d);
}
}
for (const jumbf::ContentBox& content_box : manifest_box.contents) {
if (!content_box.Holds<jumbf::SuperBox>()) {
crjson["_error"] = "Manifest contains non-SuperBox content";
continue;
}
const jumbf::SuperBox& child_box = content_box.Get<jumbf::SuperBox>();
if (child_box.description.label.value_or("") == kAssertionStoreLabel) {
crjson["assertions"] = ConvertAssertionStore(child_box);
} else if (child_box.description.label.value_or("") == kClaimV2Label) {
crjson["claim.v2"] = ConvertClaimV2(child_box);
} else if (child_box.description.label.value_or("") ==
kClaimSignatureLabel) {
crjson["signature"] = ConvertClaimSignature(child_box);
} else if (child_box.description.label.value_or("") == "c2pa.databoxes") {
// Deprecated, ignore it.
} else {
// Ignore unhandled top-level boxes in manifest.
}
}
return crjson;
}
absl::flat_hash_map<std::string, ValidationStatusSet> IndexResults(
const ValidationResultProto* absl_nullable validation_result) {
absl::flat_hash_map<std::string, ValidationStatusSet> manifests;
if (validation_result != nullptr) {
if (validation_result->has_active_manifest()) {
manifests[validation_result->active_manifest().label()] =
validation_result->active_manifest().validation();
}
for (const auto& ingredient : validation_result->ingredient_manifests()) {
manifests[ingredient.label()] = ingredient.validation();
}
}
return manifests;
}
absl::StatusOr<Json> ConvertManifestStoreToCrJsonInternal(
absl::string_view raw_manifest_store,
const ValidationResultProto* absl_nullable validation_result,
const IngredientValidationResultProto* absl_nullable
ingredient_validation_result) {
auto indexed_validation_results = IndexResults(validation_result);
// Get the spec version and trust list URI from the validation results.
std::optional<absl::string_view> spec_version;
std::optional<absl::string_view> trust_list_uri;
if (validation_result != nullptr) {
if (validation_result->has_spec_version()) {
spec_version = validation_result->spec_version();
}
if (validation_result->has_trust_list_uri()) {
trust_list_uri = validation_result->trust_list_uri();
}
} else if (ingredient_validation_result != nullptr) {
if (ingredient_validation_result->has_ingredient_validation_results()) {
if (ingredient_validation_result->ingredient_validation_results()
.has_spec_version()) {
spec_version =
ingredient_validation_result->ingredient_validation_results()
.spec_version();
}
if (ingredient_validation_result->ingredient_validation_results()
.has_trust_list_uri()) {
trust_list_uri =
ingredient_validation_result->ingredient_validation_results()
.trust_list_uri();
}
}
}
auto manifest_store_box =
jumbf::ConsumeSuperBox(&raw_manifest_store, /*recursion_limit=*/-1);
if (!manifest_store_box.ok()) {
return manifest_store_box.status();
}
// Build the crJSON.
Json crjson = Json::object();
// Add the context.
crjson["@context"] =
Json::array({"https://c2pa.org/crjson/crJSON.schema.json"});
// Add the manifests.
crjson["manifests"] = Json::array();
bool is_active_manifest = true;
for (auto it = manifest_store_box->contents.rbegin();
it != manifest_store_box->contents.rend(); ++it) {
const jumbf::ContentBox& content_box = *it;
if (!content_box.Holds<jumbf::SuperBox>()) {
crjson["_error"] = "Manifest store contains non-SuperBox content";
continue;
}
auto manifest_box = content_box.Get<jumbf::SuperBox>();
auto results = indexed_validation_results.find(
manifest_box.description.label.value_or(""));
const ValidationStatusSet* validation_status = nullptr;
if (results != indexed_validation_results.end()) {
validation_status = &results->second;
} else if (is_active_manifest && ingredient_validation_result != nullptr &&
ingredient_validation_result
->has_ingredient_validation_results()) {
validation_status =
&ingredient_validation_result->ingredient_validation_results()
.active_manifest();
}
crjson["manifests"].push_back(ConvertManifestToCrJson(
manifest_box, validation_status,
/*ingredient_deltas=*/nullptr, spec_version, trust_list_uri));
is_active_manifest = false; // Only first one is active
}
// JSON generator info.
crjson["jsonGenerator"]["name"] = "Google C2PA Toolkit";
crjson["jsonGenerator"]["version"] = "0.0.1";
return crjson;
}
} // namespace
absl::StatusOr<Json> ConvertToCrJsonWithoutValidationResults(
absl::string_view raw_manifest_store) {
return ConvertManifestStoreToCrJsonInternal(raw_manifest_store, nullptr,
nullptr);
}
absl::StatusOr<Json> ConvertToCrJson(
absl::string_view raw_manifest_store,
const ValidationResultProto& validation_result) {
return ConvertManifestStoreToCrJsonInternal(raw_manifest_store,
&validation_result, nullptr);
}
absl::StatusOr<Json> ConvertToCrJson(
absl::string_view raw_manifest_store,
const IngredientValidationResultProto& ingredient_validation_result) {
return ConvertManifestStoreToCrJsonInternal(raw_manifest_store, nullptr,
&ingredient_validation_result);
}
} // namespace credentio