blob: e0a365bcbc62e2f31c73f4c658dfdc3b14793e2b [file] [log] [blame]
[email protected]abd4cb22014-05-16 05:22:561// 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/computed_hashes.h"
6
dchengf19502002016-09-14 15:18:187#include <memory>
8#include <utility>
9
[email protected]abd4cb22014-05-16 05:22:5610#include "base/base64.h"
[email protected]abd4cb22014-05-16 05:22:5611#include "base/files/file_path.h"
thestig94712702014-09-10 07:46:5912#include "base/files/file_util.h"
[email protected]abd4cb22014-05-16 05:22:5613#include "base/json/json_reader.h"
14#include "base/json/json_writer.h"
Istiaque Ahmed588df562018-07-06 20:16:0415#include "base/metrics/histogram_macros.h"
[email protected]de00aeb2014-08-06 09:13:3916#include "base/stl_util.h"
Istiaque Ahmed588df562018-07-06 20:16:0417#include "base/timer/elapsed_timer.h"
[email protected]de00aeb2014-08-06 09:13:3918#include "base/values.h"
19#include "crypto/secure_hash.h"
20#include "crypto/sha2.h"
[email protected]abd4cb22014-05-16 05:22:5621
Jens Widellfa216f12018-02-02 08:48:3522namespace extensions {
23
24namespace computed_hashes {
[email protected]abd4cb22014-05-16 05:22:5625const char kBlockHashesKey[] = "block_hashes";
[email protected]de00aeb2014-08-06 09:13:3926const char kBlockSizeKey[] = "block_size";
27const char kFileHashesKey[] = "file_hashes";
28const char kPathKey[] = "path";
29const char kVersionKey[] = "version";
30const int kVersion = 2;
Jens Widellfa216f12018-02-02 08:48:3531} // namespace computed_hashes
[email protected]abd4cb22014-05-16 05:22:5632
Istiaque Ahmed588df562018-07-06 20:16:0433namespace {
34
35// Helper to record UMA for ComputedHashes::Reader::InitFromFile.
36// Records failure UMA if RecordSuccess() isn't explicitly called.
37class ScopedUMARecorder {
38 public:
39 ScopedUMARecorder() = default;
40
41 ~ScopedUMARecorder() {
42 if (recorded_)
43 return;
44 RecordImpl(false);
45 }
46
47 void RecordSuccess() {
48 recorded_ = true;
49 RecordImpl(true);
50 }
51
52 private:
53 void RecordImpl(bool succeeded) {
54 UMA_HISTOGRAM_BOOLEAN(
55 "Extensions.ContentVerification.ComputedHashesReadResult", succeeded);
56 if (succeeded) {
57 UMA_HISTOGRAM_TIMES(
58 "Extensions.ContentVerification.ComputedHashesInitTime",
59 timer_.Elapsed());
60 }
61 }
62
63 bool recorded_ = false;
64 base::ElapsedTimer timer_;
65 DISALLOW_COPY_AND_ASSIGN(ScopedUMARecorder);
66};
67
68} // namespace
69
[email protected]abd4cb22014-05-16 05:22:5670ComputedHashes::Reader::Reader() {
71}
[email protected]de00aeb2014-08-06 09:13:3972
[email protected]abd4cb22014-05-16 05:22:5673ComputedHashes::Reader::~Reader() {
74}
75
76bool ComputedHashes::Reader::InitFromFile(const base::FilePath& path) {
Istiaque Ahmed588df562018-07-06 20:16:0477 ScopedUMARecorder uma_recorder;
[email protected]abd4cb22014-05-16 05:22:5678 std::string contents;
79 if (!base::ReadFileToString(path, &contents))
80 return false;
81
[email protected]de00aeb2014-08-06 09:13:3982 base::DictionaryValue* top_dictionary = NULL;
dchengf5d241082016-04-21 03:43:1183 std::unique_ptr<base::Value> value(base::JSONReader::Read(contents));
[email protected]de00aeb2014-08-06 09:13:3984 if (!value.get() || !value->GetAsDictionary(&top_dictionary))
85 return false;
86
87 // For now we don't support forwards or backwards compatability in the
88 // format, so we return false on version mismatch.
89 int version = 0;
Jens Widellfa216f12018-02-02 08:48:3590 if (!top_dictionary->GetInteger(computed_hashes::kVersionKey, &version) ||
91 version != computed_hashes::kVersion)
[email protected]de00aeb2014-08-06 09:13:3992 return false;
93
94 base::ListValue* all_hashes = NULL;
Jens Widellfa216f12018-02-02 08:48:3595 if (!top_dictionary->GetList(computed_hashes::kFileHashesKey, &all_hashes))
[email protected]abd4cb22014-05-16 05:22:5696 return false;
97
98 for (size_t i = 0; i < all_hashes->GetSize(); i++) {
99 base::DictionaryValue* dictionary = NULL;
100 if (!all_hashes->GetDictionary(i, &dictionary))
101 return false;
102
103 std::string relative_path_utf8;
Jens Widellfa216f12018-02-02 08:48:35104 if (!dictionary->GetString(computed_hashes::kPathKey, &relative_path_utf8))
[email protected]abd4cb22014-05-16 05:22:56105 return false;
106
107 int block_size;
Jens Widellfa216f12018-02-02 08:48:35108 if (!dictionary->GetInteger(computed_hashes::kBlockSizeKey, &block_size))
[email protected]abd4cb22014-05-16 05:22:56109 return false;
110 if (block_size <= 0 || ((block_size % 1024) != 0)) {
111 LOG(ERROR) << "Invalid block size: " << block_size;
[email protected]abd4cb22014-05-16 05:22:56112 return false;
113 }
114
115 base::ListValue* hashes_list = NULL;
Jens Widellfa216f12018-02-02 08:48:35116 if (!dictionary->GetList(computed_hashes::kBlockHashesKey, &hashes_list))
[email protected]abd4cb22014-05-16 05:22:56117 return false;
118
119 base::FilePath relative_path =
120 base::FilePath::FromUTF8Unsafe(relative_path_utf8);
[email protected]4f9bdf62014-06-28 01:08:22121 relative_path = relative_path.NormalizePathSeparatorsTo('/');
[email protected]abd4cb22014-05-16 05:22:56122
123 data_[relative_path] = HashInfo(block_size, std::vector<std::string>());
124 std::vector<std::string>* hashes = &(data_[relative_path].second);
125
126 for (size_t j = 0; j < hashes_list->GetSize(); j++) {
127 std::string encoded;
128 if (!hashes_list->GetString(j, &encoded))
129 return false;
130
131 hashes->push_back(std::string());
132 std::string* decoded = &hashes->back();
133 if (!base::Base64Decode(encoded, decoded)) {
134 hashes->clear();
135 return false;
136 }
137 }
138 }
Istiaque Ahmed588df562018-07-06 20:16:04139 uma_recorder.RecordSuccess();
[email protected]abd4cb22014-05-16 05:22:56140 return true;
141}
142
143bool ComputedHashes::Reader::GetHashes(const base::FilePath& relative_path,
144 int* block_size,
Istiaque Ahmed9bdd9d92017-12-16 04:53:27145 std::vector<std::string>* hashes) const {
[email protected]4f9bdf62014-06-28 01:08:22146 base::FilePath path = relative_path.NormalizePathSeparatorsTo('/');
Istiaque Ahmed9bdd9d92017-12-16 04:53:27147 std::map<base::FilePath, HashInfo>::const_iterator i = data_.find(path);
asargent793691912014-10-04 01:12:21148 if (i == data_.end()) {
149 // If we didn't find the entry using exact match, it's possible the
150 // developer is using a path with some letters in the incorrect case, which
151 // happens to work on windows/osx. So try doing a linear scan to look for a
152 // case-insensitive match. In practice most extensions don't have that big
153 // a list of files so the performance penalty is probably not too big
154 // here. Also for crbug.com/29941 we plan to start warning developers when
155 // they are making this mistake, since their extension will be broken on
156 // linux/chromeos.
157 for (i = data_.begin(); i != data_.end(); ++i) {
158 const base::FilePath& entry = i->first;
159 if (base::FilePath::CompareEqualIgnoreCase(entry.value(), path.value()))
160 break;
161 }
162 if (i == data_.end())
163 return false;
164 }
Istiaque Ahmed9bdd9d92017-12-16 04:53:27165 const HashInfo& info = i->second;
[email protected]abd4cb22014-05-16 05:22:56166 *block_size = info.first;
167 *hashes = info.second;
168 return true;
169}
170
[email protected]de00aeb2014-08-06 09:13:39171ComputedHashes::Writer::Writer() : file_list_(new base::ListValue) {
[email protected]abd4cb22014-05-16 05:22:56172}
[email protected]de00aeb2014-08-06 09:13:39173
[email protected]abd4cb22014-05-16 05:22:56174ComputedHashes::Writer::~Writer() {
175}
176
177void ComputedHashes::Writer::AddHashes(const base::FilePath& relative_path,
178 int block_size,
179 const std::vector<std::string>& hashes) {
Jeremy Roman16529d0e2017-08-24 18:13:47180 auto block_hashes = std::make_unique<base::ListValue>();
jdoerrie6ff270ca2017-06-07 10:31:45181 block_hashes->GetList().reserve(hashes.size());
182 for (const auto& hash : hashes) {
183 std::string encoded;
184 base::Base64Encode(hash, &encoded);
185 block_hashes->GetList().emplace_back(std::move(encoded));
186 }
187
Jeremy Roman16529d0e2017-08-24 18:13:47188 auto dict = std::make_unique<base::DictionaryValue>();
Jens Widellfa216f12018-02-02 08:48:35189 dict->SetString(computed_hashes::kPathKey,
[email protected]4f9bdf62014-06-28 01:08:22190 relative_path.NormalizePathSeparatorsTo('/').AsUTF8Unsafe());
Jens Widellfa216f12018-02-02 08:48:35191 dict->SetInteger(computed_hashes::kBlockSizeKey, block_size);
192 dict->Set(computed_hashes::kBlockHashesKey, std::move(block_hashes));
dchengf19502002016-09-14 15:18:18193 file_list_->Append(std::move(dict));
[email protected]abd4cb22014-05-16 05:22:56194}
195
196bool ComputedHashes::Writer::WriteToFile(const base::FilePath& path) {
197 std::string json;
[email protected]de00aeb2014-08-06 09:13:39198 base::DictionaryValue top_dictionary;
Jens Widellfa216f12018-02-02 08:48:35199 top_dictionary.SetInteger(computed_hashes::kVersionKey,
200 computed_hashes::kVersion);
201 top_dictionary.Set(computed_hashes::kFileHashesKey, std::move(file_list_));
[email protected]de00aeb2014-08-06 09:13:39202
estade8d046462015-05-16 01:02:34203 if (!base::JSONWriter::Write(top_dictionary, &json))
[email protected]abd4cb22014-05-16 05:22:56204 return false;
205 int written = base::WriteFile(path, json.data(), json.size());
206 if (static_cast<unsigned>(written) != json.size()) {
[email protected]de00aeb2014-08-06 09:13:39207 LOG(ERROR) << "Error writing " << path.AsUTF8Unsafe()
[email protected]abd4cb22014-05-16 05:22:56208 << " ; write result:" << written << " expected:" << json.size();
209 return false;
210 }
211 return true;
212}
213
[email protected]de00aeb2014-08-06 09:13:39214void ComputedHashes::ComputeHashesForContent(const std::string& contents,
215 size_t block_size,
216 std::vector<std::string>* hashes) {
217 size_t offset = 0;
218 // Even when the contents is empty, we want to output at least one hash
219 // block (the hash of the empty string).
220 do {
221 const char* block_start = contents.data() + offset;
222 DCHECK(offset <= contents.size());
223 size_t bytes_to_read = std::min(contents.size() - offset, block_size);
dchengf5d241082016-04-21 03:43:11224 std::unique_ptr<crypto::SecureHash> hash(
[email protected]de00aeb2014-08-06 09:13:39225 crypto::SecureHash::Create(crypto::SecureHash::SHA256));
226 hash->Update(block_start, bytes_to_read);
227
228 hashes->push_back(std::string());
229 std::string* buffer = &(hashes->back());
230 buffer->resize(crypto::kSHA256Length);
Ryan Sleevi972b2ff2018-05-14 15:45:10231 hash->Finish(base::data(*buffer), buffer->size());
[email protected]de00aeb2014-08-06 09:13:39232
233 // If |contents| is empty, then we want to just exit here.
234 if (bytes_to_read == 0)
235 break;
236
237 offset += bytes_to_read;
238 } while (offset < contents.size());
239}
240
[email protected]abd4cb22014-05-16 05:22:56241} // namespace extensions