blob: 85f17c6feb71ce086818bb15d3f3be5654d461a8 [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 "generator/generator_impl.h"
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include "absl/functional/any_invocable.h"
#include "absl/status/status.h"
#include "absl/status/status_macros.h"
#include "absl/status/statusor.h"
#include "absl/strings/cord.h"
#include "absl/strings/string_view.h"
#include "absl/strings/substitute.h"
#include "async/async_context.h"
#include "cbor/options.h"
#include "claim/manifest_box.h"
#include "claim/manifest_box_encoder.h"
#include "claim/manifest_store_view.h"
#include "cose/sig_structure.h"
#include "generator/manifest_store_transformer.h"
#include "proto/bmff_based_hash_assertion.cbor.h"
#include "proto/bmff_based_hash_assertion.pb.h"
#include "proto/boxes_hash_assertion.cbor.h"
#include "proto/boxes_hash_assertion.pb.h"
#include "proto/collection_data_hash_assertion.cbor.h"
#include "proto/collection_data_hash_assertion.pb.h"
#include "proto/data_hash_assertion.cbor.h"
#include "proto/data_hash_assertion.pb.h"
#include "proto/generator.pb.h"
#include "utils/cbor_padding.h"
namespace credentio {
namespace {
// Current default limit on manifest store size in our validator is 10 MiB.
// Some PNG parsing code elsewhere fails if the manifest store is > 7.6 MiB.
constexpr int64_t kDefaultMaxManifestStoreSizeBytes = 7 * 1024 * 1024; // 7 MiB
template <typename T>
absl::StatusOr<size_t> GetCborSize(const T& proto,
const cbor::FromProtoOptions& options) {
ABSL_ASSIGN_OR_RETURN(auto cbor, cbor::FromProto(proto, options));
return cbor->toString().size();
}
absl::Status CheckIfPartBindingByteSizesMatched(
const PartBinding& template_part_binding,
const PartBinding& finalize_part_binding,
const cbor::FromProtoOptions& proto_to_cbor_options) {
size_t template_size;
size_t finalize_size;
switch (template_part_binding.binding_case()) {
case PartBinding::kBmffBasedHash: {
ABSL_ASSIGN_OR_RETURN(template_size,
GetCborSize(template_part_binding.bmff_based_hash(),
proto_to_cbor_options));
ABSL_ASSIGN_OR_RETURN(finalize_size,
GetCborSize(finalize_part_binding.bmff_based_hash(),
proto_to_cbor_options));
break;
}
case PartBinding::kBoxHash: {
BoxesHashAssertion template_boxes_hash = template_part_binding.box_hash();
BoxesHashAssertion finalize_boxes_hash = finalize_part_binding.box_hash();
PadBoxesHashAssertion(template_boxes_hash);
PadBoxesHashAssertion(finalize_boxes_hash);
ABSL_ASSIGN_OR_RETURN(template_size, GetCborSize(template_boxes_hash,
proto_to_cbor_options));
ABSL_ASSIGN_OR_RETURN(finalize_size, GetCborSize(finalize_boxes_hash,
proto_to_cbor_options));
break;
}
case PartBinding::kDataHash: {
DataHashAssertion template_data_hash = template_part_binding.data_hash();
DataHashAssertion finalize_data_hash = finalize_part_binding.data_hash();
PadDataHashAssertion(template_data_hash);
PadDataHashAssertion(finalize_data_hash);
ABSL_ASSIGN_OR_RETURN(template_size, GetCborSize(template_data_hash,
proto_to_cbor_options));
ABSL_ASSIGN_OR_RETURN(finalize_size, GetCborSize(finalize_data_hash,
proto_to_cbor_options));
break;
}
case PartBinding::BINDING_NOT_SET:
return absl::InvalidArgumentError(
"A part binding in the finalize request must be set");
}
if (template_size != finalize_size) {
return absl::InvalidArgumentError(
"The part binding differs from template in unsupported ways.");
}
return absl::OkStatus();
}
absl::Status CheckIfHardBindingByteSizesMatched(
const HardBinding& template_hard_binding,
const HardBinding& finalize_hard_binding,
const cbor::FromProtoOptions& proto_to_cbor_options) {
size_t template_size;
size_t finalize_size;
switch (template_hard_binding.binding_case()) {
case HardBinding::kBmffBasedHash: {
ABSL_ASSIGN_OR_RETURN(template_size,
GetCborSize(template_hard_binding.bmff_based_hash(),
proto_to_cbor_options));
ABSL_ASSIGN_OR_RETURN(finalize_size,
GetCborSize(finalize_hard_binding.bmff_based_hash(),
proto_to_cbor_options));
break;
}
case HardBinding::kBoxHash: {
BoxesHashAssertion template_boxes_hash = template_hard_binding.box_hash();
BoxesHashAssertion finalize_boxes_hash = finalize_hard_binding.box_hash();
PadBoxesHashAssertion(template_boxes_hash);
PadBoxesHashAssertion(finalize_boxes_hash);
ABSL_ASSIGN_OR_RETURN(template_size, GetCborSize(template_boxes_hash,
proto_to_cbor_options));
ABSL_ASSIGN_OR_RETURN(finalize_size, GetCborSize(finalize_boxes_hash,
proto_to_cbor_options));
break;
}
case HardBinding::kDataHash: {
DataHashAssertion template_data_hash = template_hard_binding.data_hash();
DataHashAssertion finalize_data_hash = finalize_hard_binding.data_hash();
PadDataHashAssertion(template_data_hash);
PadDataHashAssertion(finalize_data_hash);
ABSL_ASSIGN_OR_RETURN(template_size, GetCborSize(template_data_hash,
proto_to_cbor_options));
ABSL_ASSIGN_OR_RETURN(finalize_size, GetCborSize(finalize_data_hash,
proto_to_cbor_options));
break;
}
case HardBinding::kCollectionDataHash: {
ABSL_ASSIGN_OR_RETURN(
template_size,
GetCborSize(template_hard_binding.collection_data_hash(),
proto_to_cbor_options));
ABSL_ASSIGN_OR_RETURN(
finalize_size,
GetCborSize(finalize_hard_binding.collection_data_hash(),
proto_to_cbor_options));
break;
}
case HardBinding::BINDING_NOT_SET:
return absl::InvalidArgumentError(
"The hard binding in the finalize request must be set");
}
if (template_size != finalize_size) {
return absl::InvalidArgumentError(
"The final hard binding differs from template in unsupported ways.");
}
if (template_hard_binding.part_bindings_size() !=
finalize_hard_binding.part_bindings_size()) {
return absl::InvalidArgumentError(
"The number of part bindings differs between template and finalize.");
}
for (int i = 0; i < template_hard_binding.part_bindings_size(); ++i) {
ABSL_RETURN_IF_ERROR(CheckIfPartBindingByteSizesMatched(
template_hard_binding.part_bindings(i),
finalize_hard_binding.part_bindings(i), proto_to_cbor_options));
}
return absl::OkStatus();
}
absl::StatusOr<bool> HasActiveClaimTimestamp(absl::string_view manifest_store) {
ABSL_ASSIGN_OR_RETURN(ManifestStoreView view,
ManifestStoreView::Create(manifest_store));
ABSL_ASSIGN_OR_RETURN(ManifestView manifest, view.GetActiveManifest());
ABSL_ASSIGN_OR_RETURN(ClaimSignatureView claim_signature,
manifest.GetClaimSignature());
ABSL_ASSIGN_OR_RETURN(CoseSign1TaggedStructure cose_sign1,
claim_signature.GetStruct());
return cose_sign1.unprotected_header.sig_tst2.has_value();
}
} // namespace
void ManifestStoreGeneratorImpl::GenerateManifestStore(
GenerateManifestStoreRequest request, AsyncContext context,
absl::AnyInvocable<void(absl::StatusOr<GenerateManifestStoreResponse>) &&>
cb) const {
int64_t max_manifest_store_size_bytes =
request.options().max_manifest_store_size_bytes();
if (max_manifest_store_size_bytes < 0) {
AsyncResolver<GenerateManifestStoreResponse>(std::move(context),
std::move(cb))
.Return(absl::InvalidArgumentError(
"max_manifest_store_size_bytes must be non-negative"));
return;
}
if (request.options().has_max_bytes_to_shift() &&
request.options().max_bytes_to_shift() < 0) {
AsyncResolver<GenerateManifestStoreResponse>(std::move(context),
std::move(cb))
.Return(absl::InvalidArgumentError(
"max_bytes_to_shift must be non-negative"));
return;
}
if (max_manifest_store_size_bytes == 0) {
max_manifest_store_size_bytes = kDefaultMaxManifestStoreSizeBytes;
}
auto request_ptr =
std::make_shared<GenerateManifestStoreRequest>(std::move(request));
auto manifest_or = manifest_assembler_->Assemble(*request_ptr);
if (!manifest_or.ok()) {
AsyncResolver<GenerateManifestStoreResponse>(std::move(context),
std::move(cb))
.Return(manifest_or.status());
return;
}
auto manifest_box = std::make_shared<ManifestBox>(std::move(*manifest_or));
manifest_encoder_->Encode(
manifest_box.get(), request_ptr->options(), context,
[request_ptr, manifest_box, max_manifest_store_size_bytes,
manifest_store_transformer = manifest_store_transformer_,
cb = std::move(cb)](
absl::StatusOr<ManifestEncoderResult> res_or) mutable {
if (!res_or.ok()) {
std::move(cb)(res_or.status());
return;
}
absl::Cord encoded_store = res_or->box.Peek();
absl::StatusOr<bool> is_timestamped =
HasActiveClaimTimestamp(encoded_store.Flatten());
if (!is_timestamped.ok()) {
std::move(cb)(is_timestamped.status());
return;
}
if (manifest_store_transformer != nullptr) {
absl::Status transform_status =
manifest_store_transformer->Transform(&res_or->box);
if (!transform_status.ok()) {
std::move(cb)(transform_status);
return;
}
}
absl::Cord manifest_store_cord = std::move(res_or->box).AsCord();
if (manifest_store_cord.size() >
static_cast<uint64_t>(max_manifest_store_size_bytes)) {
std::move(cb)(absl::FailedPreconditionError(absl::Substitute(
"Generated manifest store too large ($0 > $1)",
manifest_store_cord.size(), max_manifest_store_size_bytes)));
return;
}
GenerateManifestStoreResponse response;
response.set_manifest_store(std::string(manifest_store_cord));
response.set_is_timestamped(*is_timestamped);
std::move(cb)(std::move(response));
});
}
void ManifestStoreGeneratorImpl::GenerateManifestStoreTemplate(
GenerateManifestStoreRequest request, AsyncContext context,
absl::AnyInvocable<void(absl::StatusOr<ManifestStoreTemplate>) &&> cb)
const {
std::string generator_context = request.SerializeAsString();
GenerateManifestStore(
std::move(request), context,
[generator_context = std::move(generator_context), cb = std::move(cb)](
absl::StatusOr<GenerateManifestStoreResponse> generated_or) mutable {
if (!generated_or.ok()) {
std::move(cb)(generated_or.status());
return;
}
ManifestStoreTemplate response;
response.set_generator_context(std::move(generator_context));
response.set_manifest_store_size(generated_or->manifest_store().size());
std::move(cb)(std::move(response));
});
}
void ManifestStoreGeneratorImpl::FinalizeManifestStore(
FinalizeManifestStoreRequest request, AsyncContext context,
absl::AnyInvocable<void(absl::StatusOr<FinalizeManifestStoreResponse>) &&>
cb) const {
if (request.manifest_store_template().generator_context().empty()) {
AsyncResolver<FinalizeManifestStoreResponse>(std::move(context),
std::move(cb))
.Return(absl::InvalidArgumentError(
"The generator_context in the manifest store template is empty"));
return;
}
if (request.manifest_store_template().manifest_store_size() < 0) {
AsyncResolver<FinalizeManifestStoreResponse>(std::move(context),
std::move(cb))
.Return(absl::InvalidArgumentError(
"manifest_store_size in template must be non-negative"));
return;
}
GenerateManifestStoreRequest generate_request;
if (!generate_request.ParseFromString(
request.manifest_store_template().generator_context())) {
AsyncResolver<FinalizeManifestStoreResponse>(std::move(context),
std::move(cb))
.Return(absl::InvalidArgumentError(
"Failed to parse the generator_context in the manifest store "
"template"));
return;
}
if (generate_request.hard_binding().binding_case() !=
request.hard_binding().binding_case()) {
AsyncResolver<FinalizeManifestStoreResponse>(std::move(context),
std::move(cb))
.Return(absl::InvalidArgumentError(
"The hard binding in the finalize request must match the original "
"request"));
return;
}
absl::Status check_status = CheckIfHardBindingByteSizesMatched(
generate_request.hard_binding(), request.hard_binding(),
proto_to_cbor_options_);
if (!check_status.ok()) {
AsyncResolver<FinalizeManifestStoreResponse>(std::move(context),
std::move(cb))
.Return(check_status);
return;
}
generate_request.mutable_hard_binding()->Swap(request.mutable_hard_binding());
size_t template_manifest_store_size =
request.manifest_store_template().manifest_store_size();
GenerateManifestStore(
std::move(generate_request), context,
[template_manifest_store_size, cb = std::move(cb)](
absl::StatusOr<GenerateManifestStoreResponse> generated_or) mutable {
if (!generated_or.ok()) {
std::move(cb)(generated_or.status());
return;
}
if (generated_or->manifest_store().size() !=
template_manifest_store_size) {
std::move(cb)(absl::InternalError(absl::Substitute(
"The generated manifest store size ($0) does not "
"match the estimated size ($1), even though the hard binding "
"sizes match. This is an internal error.",
generated_or->manifest_store().size(),
template_manifest_store_size)));
return;
}
FinalizeManifestStoreResponse response;
response.mutable_manifest_store()->swap(
*generated_or->mutable_manifest_store());
response.set_is_timestamped(generated_or->is_timestamped());
std::move(cb)(std::move(response));
});
}
} // namespace credentio