content_hash_fetcher_unittest.cc 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. // Copyright 2016 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 <memory>
  5. #include "base/bind.h"
  6. #include "base/callback.h"
  7. #include "base/callback_helpers.h"
  8. #include "base/containers/contains.h"
  9. #include "base/files/file_path.h"
  10. #include "base/files/file_util.h"
  11. #include "base/files/scoped_temp_dir.h"
  12. #include "base/memory/weak_ptr.h"
  13. #include "base/path_service.h"
  14. #include "base/version.h"
  15. #include "content/public/browser/browser_thread.h"
  16. #include "content/public/test/browser_task_environment.h"
  17. #include "extensions/browser/content_hash_fetcher.h"
  18. #include "extensions/browser/content_verifier/test_utils.h"
  19. #include "extensions/browser/extensions_test.h"
  20. #include "extensions/common/constants.h"
  21. #include "extensions/common/extension_paths.h"
  22. #include "extensions/common/file_util.h"
  23. #include "mojo/public/cpp/bindings/pending_remote.h"
  24. #include "net/http/http_status_code.h"
  25. #include "services/network/public/cpp/shared_url_loader_factory.h"
  26. #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
  27. #include "services/network/public/mojom/url_loader_factory.mojom.h"
  28. #include "services/network/public/mojom/url_response_head.mojom.h"
  29. #include "services/network/test/test_url_loader_factory.h"
  30. #include "testing/gtest/include/gtest/gtest.h"
  31. #include "third_party/zlib/google/zip.h"
  32. namespace extensions {
  33. // Installs and tests various functionality of an extension loaded without
  34. // verified_contents.json file.
  35. class ContentHashFetcherTest : public ExtensionsTest {
  36. public:
  37. ContentHashFetcherTest()
  38. // We need a real IO thread to be able to entercept the network request
  39. // for the missing verified_contents.json file.
  40. : ExtensionsTest(content::BrowserTaskEnvironment::REAL_IO_THREAD),
  41. test_shared_loader_factory_(
  42. base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
  43. &test_url_loader_factory_)) {}
  44. ContentHashFetcherTest(const ContentHashFetcherTest&) = delete;
  45. ContentHashFetcherTest& operator=(const ContentHashFetcherTest&) = delete;
  46. ~ContentHashFetcherTest() override {}
  47. bool LoadTestExtension() {
  48. test_dir_base_ = GetTestPath(
  49. base::FilePath(FILE_PATH_LITERAL("missing_verified_contents")));
  50. // We unzip the extension source to a temp directory to simulate it being
  51. // installed there, because the ContentHashFetcher will create the
  52. // _metadata/ directory within the extension install dir and write the
  53. // fetched verified_contents.json file there.
  54. extension_ =
  55. UnzipToTempDirAndLoad(test_dir_base_.AppendASCII("source.zip"));
  56. if (!extension_.get())
  57. return false;
  58. // Make sure there isn't already a verified_contents.json file there.
  59. EXPECT_FALSE(VerifiedContentsFileExists());
  60. delegate_ = std::make_unique<MockContentVerifierDelegate>();
  61. fetch_url_ = delegate_->GetSignatureFetchUrl(extension_->id(),
  62. extension_->version());
  63. return true;
  64. }
  65. std::unique_ptr<ContentHashResult> DoHashFetch() {
  66. if (!extension_.get() || !delegate_.get()) {
  67. ADD_FAILURE() << "No valid extension_ or delegate_, "
  68. "did you forget to call LoadTestExtension()?";
  69. return nullptr;
  70. }
  71. mojo::PendingRemote<network::mojom::URLLoaderFactory>
  72. url_loader_factory_remote;
  73. test_url_loader_factory_.Clone(
  74. url_loader_factory_remote.InitWithNewPipeAndPassReceiver());
  75. std::unique_ptr<ContentHashResult> result =
  76. ContentHashWaiter().CreateAndWaitForCallback(
  77. ContentHash::FetchKey(extension_->id(), extension_->path(),
  78. extension_->version(),
  79. std::move(url_loader_factory_remote),
  80. fetch_url_, delegate_->GetPublicKey()),
  81. ContentVerifierDelegate::VerifierSourceType::SIGNED_HASHES);
  82. delegate_.reset();
  83. return result;
  84. }
  85. const GURL& fetch_url() { return fetch_url_; }
  86. const base::FilePath& extension_root() { return extension_->path(); }
  87. bool VerifiedContentsFileExists() const {
  88. return base::PathExists(
  89. file_util::GetVerifiedContentsPath(extension_->path()));
  90. }
  91. base::FilePath GetResourcePath(const std::string& resource_filename) const {
  92. return test_dir_base_.AppendASCII(resource_filename);
  93. }
  94. // Registers interception of requests for |url| to respond with the contents
  95. // of the file at |response_path|.
  96. void RegisterInterception(const GURL& url,
  97. const base::FilePath& response_path) {
  98. ASSERT_TRUE(base::PathExists(response_path));
  99. std::string data;
  100. EXPECT_TRUE(ReadFileToString(response_path, &data));
  101. constexpr size_t kMaxFileSize = 1024 * 2; // Using 2k file size for safety.
  102. ASSERT_LE(data.length(), kMaxFileSize);
  103. test_url_loader_factory_.AddResponse(url.spec(), data);
  104. }
  105. void RegisterInterceptionWithFailure(const GURL& url, int net_error) {
  106. test_url_loader_factory_.AddResponse(
  107. GURL(url), network::mojom::URLResponseHead::New(), std::string(),
  108. network::URLLoaderCompletionStatus(net_error));
  109. }
  110. private:
  111. // Helper to get files from our subdirectory in the general extensions test
  112. // data dir.
  113. base::FilePath GetTestPath(const base::FilePath& relative_path) {
  114. base::FilePath base_path;
  115. EXPECT_TRUE(base::PathService::Get(extensions::DIR_TEST_DATA, &base_path));
  116. base_path = base_path.AppendASCII("content_hash_fetcher");
  117. return base_path.Append(relative_path);
  118. }
  119. // Unzips the extension source from |extension_zip| into a temporary
  120. // directory and loads it, returning the resuling Extension object.
  121. scoped_refptr<Extension> UnzipToTempDirAndLoad(
  122. const base::FilePath& extension_zip) {
  123. EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
  124. base::FilePath destination = temp_dir_.GetPath();
  125. EXPECT_TRUE(zip::Unzip(extension_zip, destination));
  126. std::string error;
  127. scoped_refptr<Extension> extension = file_util::LoadExtension(
  128. destination, mojom::ManifestLocation::kInternal, 0 /* flags */, &error);
  129. EXPECT_NE(nullptr, extension.get()) << " error:'" << error << "'";
  130. return extension;
  131. }
  132. scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_;
  133. network::TestURLLoaderFactory test_url_loader_factory_;
  134. base::ScopedTempDir temp_dir_;
  135. GURL fetch_url_;
  136. base::FilePath test_dir_base_;
  137. std::unique_ptr<MockContentVerifierDelegate> delegate_;
  138. scoped_refptr<Extension> extension_;
  139. };
  140. // This tests our ability to successfully fetch, parse, and validate a missing
  141. // verified_contents.json file for an extension.
  142. TEST_F(ContentHashFetcherTest, MissingVerifiedContents) {
  143. ASSERT_TRUE(LoadTestExtension());
  144. RegisterInterception(fetch_url(), GetResourcePath("verified_contents.json"));
  145. // Make sure the fetch was successful.
  146. std::unique_ptr<ContentHashResult> result = DoHashFetch();
  147. ASSERT_TRUE(result.get());
  148. EXPECT_TRUE(result->success);
  149. EXPECT_FALSE(result->was_cancelled);
  150. EXPECT_TRUE(result->mismatch_paths.empty());
  151. // Make sure the verified_contents.json file was written into the extension's
  152. // install dir.
  153. EXPECT_TRUE(VerifiedContentsFileExists());
  154. }
  155. // Tests that if the network fetches invalid verified_contents.json, failure
  156. // happens correctly.
  157. TEST_F(ContentHashFetcherTest, FetchInvalidVerifiedContents) {
  158. ASSERT_TRUE(LoadTestExtension());
  159. // Simulate invalid verified_contents.json fetch by providing a modified and
  160. // incorrect json file.
  161. // invalid_verified_contents.json is a modified version of
  162. // verified_contents.json, with one hash character garbled.
  163. RegisterInterception(fetch_url(),
  164. GetResourcePath("invalid_verified_contents.json"));
  165. std::unique_ptr<ContentHashResult> result = DoHashFetch();
  166. ASSERT_TRUE(result.get());
  167. EXPECT_FALSE(result->success);
  168. EXPECT_FALSE(result->was_cancelled);
  169. EXPECT_TRUE(result->mismatch_paths.empty());
  170. // TODO(lazyboy): This should be EXPECT_FALSE, we shouldn't be writing
  171. // verified_contents.json file if it didn't succeed.
  172. //// Make sure the verified_contents.json file was *not* written into the
  173. //// extension's install dir.
  174. // EXPECT_FALSE(VerifiedContentsFileExists());
  175. EXPECT_TRUE(VerifiedContentsFileExists());
  176. }
  177. // Tests that if the verified_contents.json network request 404s, failure
  178. // happens as expected.
  179. TEST_F(ContentHashFetcherTest, Fetch404VerifiedContents) {
  180. ASSERT_TRUE(LoadTestExtension());
  181. RegisterInterceptionWithFailure(fetch_url(), net::HTTP_NOT_FOUND);
  182. // Make sure the fetch was *not* successful.
  183. std::unique_ptr<ContentHashResult> result = DoHashFetch();
  184. ASSERT_TRUE(result.get());
  185. EXPECT_FALSE(result->success);
  186. EXPECT_FALSE(result->was_cancelled);
  187. EXPECT_TRUE(result->mismatch_paths.empty());
  188. // Make sure the verified_contents.json file was *not* written into the
  189. // extension's install dir.
  190. EXPECT_FALSE(VerifiedContentsFileExists());
  191. }
  192. // Similar to MissingVerifiedContents, but tests the case where the extension
  193. // actually has corruption.
  194. TEST_F(ContentHashFetcherTest, MissingVerifiedContentsAndCorrupt) {
  195. ASSERT_TRUE(LoadTestExtension());
  196. // Tamper with a file in the extension.
  197. base::FilePath script_path = extension_root().AppendASCII("script.js");
  198. std::string addition = "//hello world";
  199. ASSERT_TRUE(base::AppendToFile(script_path, addition));
  200. RegisterInterception(fetch_url(), GetResourcePath("verified_contents.json"));
  201. // Make sure the fetch was *not* successful.
  202. std::unique_ptr<ContentHashResult> result = DoHashFetch();
  203. ASSERT_NE(nullptr, result.get());
  204. EXPECT_TRUE(result->success);
  205. EXPECT_FALSE(result->was_cancelled);
  206. EXPECT_TRUE(base::Contains(result->mismatch_paths, script_path.BaseName()));
  207. // Make sure the verified_contents.json file was written into the extension's
  208. // install dir.
  209. EXPECT_TRUE(VerifiedContentsFileExists());
  210. }
  211. } // namespace extensions