| // 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 "formats/zip/hasher_data.h" | |
| #include <cstdint> | |
| #include <memory> | |
| #include <string> | |
| #include <vector> | |
| #include "absl/base/nullability.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/types/span.h" | |
| #include "bindings/input_hasher.h" | |
| #include "crypto/algorithms.h" | |
| #include "formats/zip/constants.h" | |
| #include "formats/zip/reader.h" | |
| #include "proto/collection_data_hash_assertion.pb.h" | |
| #include "riegeli/bytes/reader.h" | |
| namespace credentio { | |
| absl::StatusOr<CollectionDataHashAssertion> CollectionDataHashTemplate( | |
| absl::Span<const ZipReader::FileEntry> file_entries, HashAlgorithm alg) { | |
| CollectionDataHashAssertion assertion; | |
| assertion.set_alg(absl::StrCat(alg)); | |
| for (const ZipReader::FileEntry& file_entry : file_entries) { | |
| if (file_entry.file_name == kZipManifestFileName) { | |
| continue; | |
| } | |
| UriHashedData* uri_hashed_data = assertion.add_uris(); | |
| uri_hashed_data->set_uri(file_entry.file_name); | |
| uri_hashed_data->set_hash(std::string(HashSizeBytes(alg), 0)); | |
| } | |
| assertion.set_zip_central_directory_hash(std::string(HashSizeBytes(alg), 0)); | |
| return assertion; | |
| } | |
| absl::StatusOr<CollectionDataHashAssertion> DigestCollectionDataHash( | |
| riegeli::Reader* absl_nonnull source, HashAlgorithm alg, | |
| absl::Span<const ZipReader::FileEntry> file_entries, | |
| uint64_t central_directory_offset) { | |
| ABSL_ASSIGN_OR_RETURN(std::vector<ZipReader::FileEntry> sorted_entries, | |
| ZipReader::SortFileEntries(file_entries)); | |
| if (!source->Size().has_value()) { | |
| return absl::InternalError("source->Size() returned no value"); | |
| } | |
| CollectionDataHashAssertion assertion; | |
| assertion.set_alg(absl::StrCat(alg)); | |
| const ZipReader::FileEntry* manifest_file_entry = nullptr; | |
| for (const ZipReader::FileEntry& file_entry : sorted_entries) { | |
| if (file_entry.file_name == kZipManifestFileName) { | |
| manifest_file_entry = &file_entry; | |
| continue; | |
| } | |
| UriHashedData* uri_hashed_data = assertion.add_uris(); | |
| uri_hashed_data->set_uri(file_entry.file_name); | |
| int64_t entry_end = | |
| file_entry.file_range.offset + file_entry.file_range.length; | |
| if (file_entry.has_data_descriptor) { | |
| if (!source->Seek(entry_end) || source->pos() != entry_end) { | |
| return absl::InternalError("Failed to seek to entry end."); | |
| } | |
| std::string buffer; | |
| if (!source->Read(kZipDataDescriptorSignature.size(), buffer)) { | |
| return absl::InternalError("Failed to read data descriptor signature."); | |
| } | |
| if (buffer == kZipDataDescriptorSignature) { | |
| entry_end += kDataDescriptorWithSignatureSize; | |
| } else { | |
| entry_end += kDataDescriptorSize; | |
| } | |
| } | |
| ABSL_ASSIGN_OR_RETURN(std::unique_ptr<InputHasher> hasher, | |
| InputHasher::Create(alg)); | |
| ABSL_RETURN_IF_ERROR( | |
| hasher->Update(*source, file_entry.local_file_header_offset, | |
| entry_end - file_entry.local_file_header_offset)); | |
| uri_hashed_data->set_hash(hasher->Digest()); | |
| } | |
| ABSL_ASSIGN_OR_RETURN(std::unique_ptr<InputHasher> hasher, | |
| InputHasher::Create(alg)); | |
| if (!source->Seek(central_directory_offset) || | |
| source->pos() != central_directory_offset) { | |
| return absl::InternalError("Failed to seek to central directory offset."); | |
| } | |
| if (manifest_file_entry == nullptr) { | |
| return absl::InternalError("No manifest file entry found."); | |
| } | |
| uint64_t crc32_offset = | |
| manifest_file_entry->central_directory_header_offset + 16; | |
| ABSL_RETURN_IF_ERROR(hasher->Update(*source, central_directory_offset, | |
| crc32_offset - central_directory_offset)); | |
| uint64_t after_crc32_offset = crc32_offset + 4; | |
| ABSL_RETURN_IF_ERROR( | |
| hasher->Update(*source, after_crc32_offset, | |
| source->Size().value() - after_crc32_offset)); | |
| assertion.set_zip_central_directory_hash(hasher->Digest()); | |
| return assertion; | |
| } | |
| } // namespace credentio |