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 
5 #include "extensions/browser/content_hash_reader.h"
6 
7 #include "base/files/file_util.h"
8 #include "base/memory/ptr_util.h"
9 #include "base/metrics/histogram_macros.h"
10 #include "base/stl_util.h"
11 #include "base/strings/string_util.h"
12 #include "base/timer/elapsed_timer.h"
13 #include "base/values.h"
14 #include "crypto/sha2.h"
15 #include "extensions/browser/computed_hashes.h"
16 #include "extensions/browser/content_hash_tree.h"
17 #include "extensions/browser/content_verifier/content_hash.h"
18 #include "extensions/browser/verified_contents.h"
19 
20 namespace extensions {
21 
ContentHashReader(InitStatus status)22 ContentHashReader::ContentHashReader(InitStatus status) : status_(status) {}
23 
24 ContentHashReader::~ContentHashReader() = default;
25 
26 // static
Create(const base::FilePath & relative_path,const scoped_refptr<const ContentHash> & content_hash)27 std::unique_ptr<const ContentHashReader> ContentHashReader::Create(
28     const base::FilePath& relative_path,
29     const scoped_refptr<const ContentHash>& content_hash) {
30   base::ElapsedTimer timer;
31 
32   ComputedHashes::Status hashes_status = content_hash->computed_hashes_status();
33   if (hashes_status == ComputedHashes::Status::UNKNOWN ||
34       hashes_status == ComputedHashes::Status::READ_FAILED) {
35     // Failure: no hashes at all.
36     return base::WrapUnique(new ContentHashReader(InitStatus::HASHES_MISSING));
37   }
38   if (hashes_status == ComputedHashes::Status::PARSE_FAILED) {
39     // Failure: hashes are unreadable.
40     return base::WrapUnique(new ContentHashReader(InitStatus::HASHES_DAMAGED));
41   }
42   DCHECK_EQ(ComputedHashes::Status::SUCCESS, hashes_status);
43 
44   const ComputedHashes& computed_hashes = content_hash->computed_hashes();
45   base::Optional<std::string> root;
46 
47   int block_size;
48   std::vector<std::string> block_hashes;
49 
50   if (computed_hashes.GetHashes(relative_path, &block_size, &block_hashes) &&
51       block_size % crypto::kSHA256Length == 0) {
52     root =
53         ComputeTreeHashRoot(block_hashes, block_size / crypto::kSHA256Length);
54   }
55 
56   ContentHash::TreeHashVerificationResult verification =
57       content_hash->VerifyTreeHashRoot(relative_path,
58                                        base::OptionalOrNullptr(root));
59 
60   // Extensions sometimes request resources that do not have an entry in
61   // computed_hashes.json or verified_content.json. This can happen, for
62   // example, when an extension sends an XHR to a resource. This should not be
63   // considered as a failure.
64   if (verification != ContentHash::TreeHashVerificationResult::SUCCESS) {
65     base::FilePath full_path =
66         content_hash->extension_root().Append(relative_path);
67     // Making a request to a non-existent file or to a directory should not
68     // result in content verification failure.
69     // TODO(proberge): This logic could be simplified if |content_verify_job|
70     // kept track of whether the file being verified was successfully read.
71     // A content verification failure should be triggered if there is a mismatch
72     // between the file read state and the existence of verification hashes.
73     if (verification == ContentHash::TreeHashVerificationResult::NO_ENTRY &&
74         (!base::PathExists(full_path) || base::DirectoryExists(full_path))) {
75       // Expected failure: no hashes for non-existing resource.
76       return base::WrapUnique(new ContentHashReader(
77           InitStatus::NO_HASHES_FOR_NON_EXISTING_RESOURCE));
78     }
79 
80     // Failure: no hashes when resource need them.
81     return base::WrapUnique(
82         new ContentHashReader(InitStatus::NO_HASHES_FOR_RESOURCE));
83   }
84 
85   auto hash_reader =
86       base::WrapUnique(new ContentHashReader(InitStatus::SUCCESS));
87   hash_reader->block_size_ = block_size;
88   hash_reader->hashes_ = std::move(block_hashes);
89 
90   UMA_HISTOGRAM_TIMES("ExtensionContentHashReader.InitLatency",
91                       timer.Elapsed());
92   return hash_reader;  // Success.
93 }
94 
block_count() const95 int ContentHashReader::block_count() const {
96   return hashes_.size();
97 }
98 
block_size() const99 int ContentHashReader::block_size() const {
100   return block_size_;
101 }
102 
GetHashForBlock(int block_index,const std::string ** result) const103 bool ContentHashReader::GetHashForBlock(int block_index,
104                                         const std::string** result) const {
105   if (status_ != InitStatus::SUCCESS)
106     return false;
107   DCHECK(block_index >= 0);
108 
109   if (static_cast<unsigned>(block_index) >= hashes_.size())
110     return false;
111   *result = &hashes_[block_index];
112 
113   return true;
114 }
115 
116 }  // namespace extensions
117