computed_hashes_unittest.cc 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. // Copyright 2014 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "extensions/browser/computed_hashes.h"
  5. #include "base/base64.h"
  6. #include "base/files/file_path.h"
  7. #include "base/files/scoped_temp_dir.h"
  8. #include "base/strings/stringprintf.h"
  9. #include "build/build_config.h"
  10. #include "crypto/sha2.h"
  11. #include "extensions/browser/content_verifier/content_verifier_utils.h"
  12. #include "extensions/common/constants.h"
  13. #include "testing/gtest/include/gtest/gtest.h"
  14. namespace {
  15. constexpr bool kIsDotSpaceSuffixIgnored =
  16. extensions::content_verifier_utils::IsDotSpaceFilenameSuffixIgnored();
  17. constexpr bool kIsFileAccessCaseInsensitive =
  18. !extensions::content_verifier_utils::IsFileAccessCaseSensitive();
  19. // Helper to return base64 encode result by value.
  20. std::string Base64Encode(const std::string& data) {
  21. std::string result;
  22. base::Base64Encode(data, &result);
  23. return result;
  24. }
  25. struct HashInfo {
  26. base::FilePath path;
  27. int block_size;
  28. std::vector<std::string> hashes;
  29. };
  30. testing::AssertionResult WriteThenReadComputedHashes(
  31. const std::vector<HashInfo>& hash_infos,
  32. extensions::ComputedHashes* result) {
  33. base::ScopedTempDir scoped_dir;
  34. if (!scoped_dir.CreateUniqueTempDir())
  35. return testing::AssertionFailure() << "Failed to create temp dir.";
  36. base::FilePath computed_hashes_path =
  37. scoped_dir.GetPath().AppendASCII("computed_hashes.json");
  38. extensions::ComputedHashes::Data computed_hashes_data;
  39. for (const auto& info : hash_infos)
  40. computed_hashes_data.Add(info.path, info.block_size, info.hashes);
  41. if (!extensions::ComputedHashes(std::move(computed_hashes_data))
  42. .WriteToFile(computed_hashes_path)) {
  43. return testing::AssertionFailure()
  44. << "Failed to write computed_hashes.json";
  45. }
  46. extensions::ComputedHashes::Status computed_hashes_status;
  47. absl::optional<extensions::ComputedHashes> computed_hashes =
  48. extensions::ComputedHashes::CreateFromFile(computed_hashes_path,
  49. &computed_hashes_status);
  50. if (!computed_hashes)
  51. return testing::AssertionFailure()
  52. << "Failed to read computed_hashes.json (status: "
  53. << static_cast<int>(computed_hashes_status) << ")";
  54. *result = std::move(computed_hashes.value());
  55. return testing::AssertionSuccess();
  56. }
  57. } // namespace
  58. namespace extensions {
  59. TEST(ComputedHashesTest, ComputedHashes) {
  60. // We'll add hashes for 2 files, one of which uses a subdirectory
  61. // path. The first file will have a list of 1 block hash, and the
  62. // second file will have 2 block hashes.
  63. base::FilePath path1(FILE_PATH_LITERAL("foo.txt"));
  64. base::FilePath path2 =
  65. base::FilePath(FILE_PATH_LITERAL("foo")).AppendASCII("bar.txt");
  66. std::vector<std::string> hashes1 = {crypto::SHA256HashString("first")};
  67. std::vector<std::string> hashes2 = {crypto::SHA256HashString("second"),
  68. crypto::SHA256HashString("third")};
  69. const int kBlockSize1 = 4096;
  70. const int kBlockSize2 = 2048;
  71. ComputedHashes computed_hashes{ComputedHashes::Data()};
  72. ASSERT_TRUE(WriteThenReadComputedHashes(
  73. {{path1, kBlockSize1, hashes1}, {path2, kBlockSize2, hashes2}},
  74. &computed_hashes));
  75. // After reading hashes back assert that we got what we wrote.
  76. std::vector<std::string> read_hashes1;
  77. std::vector<std::string> read_hashes2;
  78. int block_size = 0;
  79. EXPECT_TRUE(computed_hashes.GetHashes(path1, &block_size, &read_hashes1));
  80. EXPECT_EQ(block_size, 4096);
  81. block_size = 0;
  82. EXPECT_TRUE(computed_hashes.GetHashes(path2, &block_size, &read_hashes2));
  83. EXPECT_EQ(block_size, 2048);
  84. EXPECT_EQ(hashes1, read_hashes1);
  85. EXPECT_EQ(hashes2, read_hashes2);
  86. // Make sure we can lookup hashes for a file using incorrect case
  87. base::FilePath path1_badcase(FILE_PATH_LITERAL("FoO.txt"));
  88. std::vector<std::string> read_hashes1_badcase;
  89. EXPECT_EQ(kIsFileAccessCaseInsensitive,
  90. computed_hashes.GetHashes(path1_badcase, &block_size,
  91. &read_hashes1_badcase));
  92. if (kIsFileAccessCaseInsensitive) {
  93. EXPECT_EQ(4096, block_size);
  94. EXPECT_EQ(hashes1, read_hashes1_badcase);
  95. }
  96. // Finally make sure that we can retrieve the hashes for the subdir
  97. // path even when that path contains forward slashes (on windows).
  98. base::FilePath path2_fwd_slashes =
  99. base::FilePath::FromUTF8Unsafe("foo/bar.txt");
  100. block_size = 0;
  101. EXPECT_TRUE(
  102. computed_hashes.GetHashes(path2_fwd_slashes, &block_size, &read_hashes2));
  103. EXPECT_EQ(hashes2, read_hashes2);
  104. }
  105. // Note: the expected hashes used in this test were generated using linux
  106. // command line tools. E.g., from a bash prompt:
  107. // $ printf "hello world" | openssl dgst -sha256 -binary | base64
  108. //
  109. // The file with multiple-blocks expectations were generated by doing:
  110. // $ for i in `seq 500 ; do printf "hello world" ; done > hello.txt
  111. // $ dd if=hello.txt bs=4096 count=1 | openssl dgst -sha256 -binary | base64
  112. // $ dd if=hello.txt skip=1 bs=4096 count=1 |
  113. // openssl dgst -sha256 -binary | base64
  114. TEST(ComputedHashesTest, GetHashesForContent) {
  115. const int block_size = 4096;
  116. // Simple short input.
  117. std::string content1 = "hello world";
  118. std::string content1_expected_hash =
  119. "uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=";
  120. std::vector<std::string> hashes1 =
  121. ComputedHashes::GetHashesForContent(content1, block_size);
  122. ASSERT_EQ(1u, hashes1.size());
  123. EXPECT_EQ(content1_expected_hash, Base64Encode(hashes1[0]));
  124. // Multiple blocks input.
  125. std::string content2;
  126. for (int i = 0; i < 500; i++)
  127. content2 += "hello world";
  128. const char* content2_expected_hashes[] = {
  129. "bvtt5hXo8xvHrlzGAhhoqPL/r+4zJXHx+6wAvkv15V8=",
  130. "lTD45F7P6I/HOdi8u7FLRA4qzAYL+7xSNVeusG6MJI0="};
  131. std::vector<std::string> hashes2 =
  132. ComputedHashes::GetHashesForContent(content2, block_size);
  133. ASSERT_EQ(2u, hashes2.size());
  134. EXPECT_EQ(content2_expected_hashes[0], Base64Encode(hashes2[0]));
  135. EXPECT_EQ(content2_expected_hashes[1], Base64Encode(hashes2[1]));
  136. // Now an empty input.
  137. std::string content3;
  138. std::vector<std::string> hashes3 =
  139. ComputedHashes::GetHashesForContent(content3, block_size);
  140. ASSERT_EQ(1u, hashes3.size());
  141. ASSERT_EQ(std::string("47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="),
  142. Base64Encode(hashes3[0]));
  143. }
  144. // Tests that dot/space path suffixes are treated correctly in
  145. // ComputedHashes::InitFromFile.
  146. //
  147. // Regression test for https://crbug.com/696208.
  148. TEST(ComputedHashesTest, DotSpaceSuffix) {
  149. const std::string hash_value = crypto::SHA256HashString("test");
  150. ComputedHashes computed_hashes{ComputedHashes::Data()};
  151. // Add hashes for "foo.html" to computed_hashes.json.
  152. ASSERT_TRUE(WriteThenReadComputedHashes(
  153. {
  154. {base::FilePath(FILE_PATH_LITERAL("foo.html")),
  155. extension_misc::kContentVerificationDefaultBlockSize,
  156. {hash_value}},
  157. },
  158. &computed_hashes));
  159. struct TestCase {
  160. const char* path;
  161. bool expect_hash;
  162. std::string ToString() const {
  163. return base::StringPrintf("path = %s, expect_hash = %d", path,
  164. expect_hash);
  165. }
  166. } test_cases[] = {
  167. // Sanity check: existing file.
  168. {"foo.html", true},
  169. // Sanity check: non existent file.
  170. {"notfound.html", false},
  171. // Path with "." suffix, along with incorrect case for the same.
  172. {"foo.html.", kIsDotSpaceSuffixIgnored},
  173. {"fOo.html.", kIsDotSpaceSuffixIgnored},
  174. // Path with " " suffix, along with incorrect case for the same.
  175. {"foo.html ", kIsDotSpaceSuffixIgnored},
  176. {"fOo.html ", kIsDotSpaceSuffixIgnored},
  177. // Path with ". " suffix, along with incorrect case for the same.
  178. {"foo.html. ", kIsDotSpaceSuffixIgnored},
  179. {"fOo.html. ", kIsDotSpaceSuffixIgnored},
  180. // Path with " ." suffix, along with incorrect case for the same.
  181. {"foo.html .", kIsDotSpaceSuffixIgnored},
  182. {"fOo.html .", kIsDotSpaceSuffixIgnored},
  183. };
  184. for (const auto& test_case : test_cases) {
  185. SCOPED_TRACE(test_case.ToString());
  186. int block_size = 0;
  187. std::vector<std::string> read_hashes;
  188. EXPECT_EQ(
  189. test_case.expect_hash,
  190. computed_hashes.GetHashes(base::FilePath().AppendASCII(test_case.path),
  191. &block_size, &read_hashes));
  192. if (test_case.expect_hash) {
  193. EXPECT_EQ(block_size,
  194. extension_misc::kContentVerificationDefaultBlockSize);
  195. ASSERT_EQ(1u, read_hashes.size());
  196. EXPECT_EQ(hash_value, read_hashes[0]);
  197. }
  198. }
  199. }
  200. } // namespace extensions