asar_file_validator.cc 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. // Copyright (c) 2021 Slack Technologies, Inc.
  2. // Use of this source code is governed by the MIT license that can be
  3. // found in the LICENSE file.
  4. #include "shell/browser/net/asar/asar_file_validator.h"
  5. #include <algorithm>
  6. #include <string>
  7. #include <utility>
  8. #include <vector>
  9. #include "base/logging.h"
  10. #include "base/strings/string_number_conversions.h"
  11. #include "base/strings/string_util.h"
  12. #include "crypto/sha2.h"
  13. namespace asar {
  14. AsarFileValidator::AsarFileValidator(IntegrityPayload integrity,
  15. base::File file)
  16. : file_(std::move(file)), integrity_(std::move(integrity)) {
  17. current_block_ = 0;
  18. max_block_ = integrity_.blocks.size() - 1;
  19. }
  20. AsarFileValidator::~AsarFileValidator() = default;
  21. void AsarFileValidator::OnRead(base::span<char> buffer,
  22. mojo::FileDataSource::ReadResult* result) {
  23. DCHECK(!done_reading_);
  24. uint64_t buffer_size = result->bytes_read;
  25. // Compute how many bytes we should hash, and add them to the current hash.
  26. uint32_t block_size = integrity_.block_size;
  27. uint64_t bytes_added = 0;
  28. while (bytes_added < buffer_size) {
  29. if (current_block_ > max_block_) {
  30. LOG(FATAL)
  31. << "Unexpected number of blocks while validating ASAR file stream";
  32. }
  33. // Create a hash if we don't have one yet
  34. if (!current_hash_) {
  35. current_hash_byte_count_ = 0;
  36. switch (integrity_.algorithm) {
  37. case HashAlgorithm::kSHA256:
  38. current_hash_ =
  39. crypto::SecureHash::Create(crypto::SecureHash::SHA256);
  40. break;
  41. case HashAlgorithm::kNone:
  42. CHECK(false);
  43. break;
  44. }
  45. }
  46. // Compute how many bytes we should hash, and add them to the current hash.
  47. // We need to either add just enough bytes to fill up a block (block_size -
  48. // current_bytes) or use every remaining byte (buffer_size - bytes_added)
  49. int bytes_to_hash = std::min(block_size - current_hash_byte_count_,
  50. buffer_size - bytes_added);
  51. DCHECK_GT(bytes_to_hash, 0);
  52. current_hash_->Update(buffer.data() + bytes_added, bytes_to_hash);
  53. bytes_added += bytes_to_hash;
  54. current_hash_byte_count_ += bytes_to_hash;
  55. total_hash_byte_count_ += bytes_to_hash;
  56. if (current_hash_byte_count_ == block_size && !FinishBlock()) {
  57. LOG(FATAL) << "Failed to validate block while streaming ASAR file: "
  58. << current_block_;
  59. }
  60. }
  61. }
  62. bool AsarFileValidator::FinishBlock() {
  63. if (current_hash_byte_count_ == 0) {
  64. if (!done_reading_ || current_block_ > max_block_) {
  65. return true;
  66. }
  67. }
  68. if (!current_hash_) {
  69. // This happens when we fail to read the resource. Compute empty content's
  70. // hash in this case.
  71. current_hash_ = crypto::SecureHash::Create(crypto::SecureHash::SHA256);
  72. }
  73. uint8_t actual[crypto::kSHA256Length];
  74. // If the file reader is done we need to make sure we've either read up to the
  75. // end of the file (the check below) or up to the end of a block_size byte
  76. // boundary. If the below check fails we compute the next block boundary, how
  77. // many bytes are needed to get there and then we manually read those bytes
  78. // from our own file handle ensuring the data producer is unaware but we can
  79. // validate the hash still.
  80. if (done_reading_ &&
  81. total_hash_byte_count_ - extra_read_ != read_max_ - read_start_) {
  82. uint64_t bytes_needed = std::min(
  83. integrity_.block_size - current_hash_byte_count_,
  84. read_max_ - read_start_ - total_hash_byte_count_ + extra_read_);
  85. uint64_t offset = read_start_ + total_hash_byte_count_ - extra_read_;
  86. std::vector<uint8_t> abandoned_buffer(bytes_needed);
  87. if (!file_.ReadAndCheck(offset, abandoned_buffer)) {
  88. LOG(FATAL) << "Failed to read required portion of streamed ASAR archive";
  89. }
  90. current_hash_->Update(&abandoned_buffer.front(), bytes_needed);
  91. }
  92. current_hash_->Finish(actual, sizeof(actual));
  93. current_hash_.reset();
  94. current_hash_byte_count_ = 0;
  95. const std::string expected_hash = integrity_.blocks[current_block_];
  96. const std::string actual_hex_hash =
  97. base::ToLowerASCII(base::HexEncode(actual, sizeof(actual)));
  98. if (expected_hash != actual_hex_hash) {
  99. return false;
  100. }
  101. current_block_++;
  102. return true;
  103. }
  104. void AsarFileValidator::OnDone() {
  105. DCHECK(!done_reading_);
  106. done_reading_ = true;
  107. if (!FinishBlock()) {
  108. LOG(FATAL) << "Failed to validate block while ending ASAR file stream: "
  109. << current_block_;
  110. }
  111. }
  112. void AsarFileValidator::SetRange(uint64_t read_start,
  113. uint64_t extra_read,
  114. uint64_t read_max) {
  115. read_start_ = read_start;
  116. extra_read_ = extra_read;
  117. read_max_ = read_max;
  118. }
  119. void AsarFileValidator::SetCurrentBlock(int current_block) {
  120. current_block_ = current_block;
  121. }
  122. } // namespace asar