asar_file_validator.cc 4.6 KB

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