archive_win.cc 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. // Copyright 2023 Slack Technologies, Inc.
  2. // Contributors: Weiyun Dai (https://github.com/WeiyunD/), Andrew Lay
  3. // (https://github.com/guohaolay) Use of this source code is governed by the MIT
  4. // license that can be found in the LICENSE file.
  5. #include "shell/common/asar/archive.h"
  6. #include <algorithm>
  7. #include <sstream>
  8. #include "base/base_paths.h"
  9. #include "base/json/json_reader.h"
  10. #include "base/logging.h"
  11. #include "base/no_destructor.h"
  12. #include "base/path_service.h"
  13. #include "base/strings/string_util.h"
  14. #include "base/strings/string_util_win.h"
  15. #include "base/strings/utf_string_conversions.h"
  16. #include "shell/common/asar/asar_util.h"
  17. namespace asar {
  18. const wchar_t kIntegrityCheckResourceType[] = L"Integrity";
  19. const wchar_t kIntegrityCheckResourceItem[] = L"ElectronAsar";
  20. std::optional<base::FilePath> Archive::RelativePath() const {
  21. base::FilePath exe_path;
  22. if (!base::PathService::Get(base::FILE_EXE, &exe_path)) {
  23. LOG(FATAL) << "Couldn't get exe file path";
  24. }
  25. base::FilePath relative_path;
  26. if (!exe_path.DirName().AppendRelativePath(path_, &relative_path)) {
  27. return std::nullopt;
  28. }
  29. return relative_path;
  30. }
  31. std::optional<std::unordered_map<std::string, IntegrityPayload>>
  32. LoadIntegrityConfigCache() {
  33. static base::NoDestructor<
  34. std::optional<std::unordered_map<std::string, IntegrityPayload>>>
  35. integrity_config_cache;
  36. // Skip loading if cache is already loaded
  37. if (integrity_config_cache->has_value()) {
  38. return *integrity_config_cache;
  39. }
  40. // Init cache
  41. *integrity_config_cache = std::unordered_map<std::string, IntegrityPayload>();
  42. // Load integrity config from exe resource
  43. HMODULE module_handle = ::GetModuleHandle(NULL);
  44. HRSRC resource = ::FindResource(module_handle, kIntegrityCheckResourceItem,
  45. kIntegrityCheckResourceType);
  46. if (!resource) {
  47. PLOG(FATAL) << "FindResource failed.";
  48. }
  49. HGLOBAL rcData = ::LoadResource(module_handle, resource);
  50. if (!rcData) {
  51. PLOG(FATAL) << "LoadResource failed.";
  52. }
  53. auto* res_data = static_cast<const char*>(::LockResource(rcData));
  54. int res_size = SizeofResource(module_handle, resource);
  55. if (!res_data) {
  56. PLOG(FATAL) << "Failed to integrity config from exe resource.";
  57. }
  58. if (!res_size) {
  59. PLOG(FATAL) << "Unexpected empty integrity config from exe resource.";
  60. }
  61. // Parse integrity config payload
  62. std::string integrity_config_payload = std::string(res_data, res_size);
  63. std::optional<base::Value> root =
  64. base::JSONReader::Read(integrity_config_payload);
  65. if (!root.has_value()) {
  66. LOG(FATAL) << "Invalid integrity config: NOT a valid JSON.";
  67. }
  68. const base::Value::List* file_configs = root.value().GetIfList();
  69. if (!file_configs) {
  70. LOG(FATAL) << "Invalid integrity config: NOT a list.";
  71. }
  72. // Parse each individual file integrity config
  73. for (size_t i = 0; i < file_configs->size(); i++) {
  74. // Skip invalid file configs
  75. const base::Value::Dict* ele_dict = (*file_configs)[i].GetIfDict();
  76. if (!ele_dict) {
  77. LOG(WARNING) << "Skip config " << i << ": NOT a valid dict";
  78. continue;
  79. }
  80. const std::string* file = ele_dict->FindString("file");
  81. if (!file || file->empty()) {
  82. LOG(WARNING) << "Skip config " << i << ": Invalid file";
  83. continue;
  84. }
  85. const std::string* alg = ele_dict->FindString("alg");
  86. if (!alg || base::ToLowerASCII(*alg) != "sha256") {
  87. LOG(WARNING) << "Skip config " << i << ": Invalid alg";
  88. continue;
  89. }
  90. const std::string* value = ele_dict->FindString("value");
  91. if (!value || value->empty()) {
  92. LOG(WARNING) << "Skip config " << i << ": Invalid hash value";
  93. continue;
  94. }
  95. // Add valid file config into cache
  96. IntegrityPayload header_integrity;
  97. header_integrity.algorithm = HashAlgorithm::kSHA256;
  98. header_integrity.hash = base::ToLowerASCII(*value);
  99. integrity_config_cache->value()[base::ToLowerASCII(*file)] =
  100. std::move(header_integrity);
  101. }
  102. return *integrity_config_cache;
  103. }
  104. std::optional<IntegrityPayload> Archive::HeaderIntegrity() const {
  105. std::optional<base::FilePath> relative_path = RelativePath();
  106. // Callers should have already asserted this
  107. CHECK(relative_path.has_value());
  108. // Load integrity config from exe resource
  109. std::optional<std::unordered_map<std::string, IntegrityPayload>>
  110. integrity_config = LoadIntegrityConfigCache();
  111. if (!integrity_config.has_value()) {
  112. LOG(WARNING) << "Failed to integrity config from exe resource.";
  113. return std::nullopt;
  114. }
  115. // Convert Window rel path to UTF8 lower case
  116. std::string rel_path_utf8 = base::WideToUTF8(relative_path.value().value());
  117. rel_path_utf8 = base::ToLowerASCII(rel_path_utf8);
  118. // Find file integrity config
  119. auto iter = integrity_config.value().find(rel_path_utf8);
  120. if (iter == integrity_config.value().end()) {
  121. LOG(FATAL) << "Failed to find file integrity info for " << rel_path_utf8;
  122. }
  123. return iter->second;
  124. }
  125. } // namespace asar