blob: 1533306bd08606ac962b148dcd335b4d8ab365e5 [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"
[email protected]de00aeb2014-08-06 09:13:3915#include "base/stl_util.h"
Istiaque Ahmed588df562018-07-06 20:16:0416#include "base/timer/elapsed_timer.h"
[email protected]de00aeb2014-08-06 09:13:3917#include "base/values.h"
18#include "crypto/secure_hash.h"
19#include "crypto/sha2.h"
Oleg Davydov44be148d2019-08-23 09:42:3920#include "extensions/browser/content_verifier/scoped_uma_recorder.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
Oleg Davydov44be148d2019-08-23 09:42:3935const char kUMAComputedHashesReadResult[] =
36 "Extensions.ContentVerification.ComputedHashesReadResult";
37const char kUMAComputedHashesInitTime[] =
38 "Extensions.ContentVerification.ComputedHashesInitTime";
Istiaque Ahmed588df562018-07-06 20:16:0439
40} // namespace
41
[email protected]abd4cb22014-05-16 05:22:5642ComputedHashes::Reader::Reader() {
43}
[email protected]de00aeb2014-08-06 09:13:3944
[email protected]abd4cb22014-05-16 05:22:5645ComputedHashes::Reader::~Reader() {
46}
47
48bool ComputedHashes::Reader::InitFromFile(const base::FilePath& path) {
Oleg Davydov44be148d2019-08-23 09:42:3949 ScopedUMARecorder<kUMAComputedHashesReadResult, kUMAComputedHashesInitTime>
50 uma_recorder;
[email protected]abd4cb22014-05-16 05:22:5651 std::string contents;
52 if (!base::ReadFileToString(path, &contents))
53 return false;
54
Sungguk Limf70d1232019-06-26 20:18:1155 base::Optional<base::Value> top_dictionary = base::JSONReader::Read(contents);
56 if (!top_dictionary || !top_dictionary->is_dict())
[email protected]de00aeb2014-08-06 09:13:3957 return false;
58
59 // For now we don't support forwards or backwards compatability in the
60 // format, so we return false on version mismatch.
Sungguk Limf70d1232019-06-26 20:18:1161 base::Optional<int> version =
62 top_dictionary->FindIntKey(computed_hashes::kVersionKey);
63 if (!version || *version != computed_hashes::kVersion)
[email protected]de00aeb2014-08-06 09:13:3964 return false;
65
Sungguk Limf70d1232019-06-26 20:18:1166 const base::Value* all_hashes =
67 top_dictionary->FindListKey(computed_hashes::kFileHashesKey);
68 if (!all_hashes)
[email protected]abd4cb22014-05-16 05:22:5669 return false;
70
Sungguk Limf70d1232019-06-26 20:18:1171 for (const base::Value& file_hash : all_hashes->GetList()) {
72 if (!file_hash.is_dict())
[email protected]abd4cb22014-05-16 05:22:5673 return false;
74
Sungguk Limf70d1232019-06-26 20:18:1175 const std::string* relative_path_utf8 =
76 file_hash.FindStringKey(computed_hashes::kPathKey);
77 if (!relative_path_utf8)
[email protected]abd4cb22014-05-16 05:22:5678 return false;
79
Sungguk Limf70d1232019-06-26 20:18:1180 base::Optional<int> block_size =
81 file_hash.FindIntKey(computed_hashes::kBlockSizeKey);
82 if (!block_size)
[email protected]abd4cb22014-05-16 05:22:5683 return false;
Sungguk Limf70d1232019-06-26 20:18:1184 if (*block_size <= 0 || ((*block_size % 1024) != 0)) {
85 LOG(ERROR) << "Invalid block size: " << *block_size;
[email protected]abd4cb22014-05-16 05:22:5686 return false;
87 }
88
Sungguk Limf70d1232019-06-26 20:18:1189 const base::Value* block_hashes =
90 file_hash.FindListKey(computed_hashes::kBlockHashesKey);
91 if (!block_hashes)
[email protected]abd4cb22014-05-16 05:22:5692 return false;
93
Jan Wilken Dörrie53e009b2019-09-09 14:17:4194 base::span<const base::Value> hashes_list = block_hashes->GetList();
Sungguk Limf70d1232019-06-26 20:18:1195
[email protected]abd4cb22014-05-16 05:22:5696 base::FilePath relative_path =
Sungguk Limf70d1232019-06-26 20:18:1197 base::FilePath::FromUTF8Unsafe(*relative_path_utf8);
[email protected]4f9bdf62014-06-28 01:08:2298 relative_path = relative_path.NormalizePathSeparatorsTo('/');
[email protected]abd4cb22014-05-16 05:22:5699
Sungguk Limf70d1232019-06-26 20:18:11100 data_[relative_path] = HashInfo(*block_size, std::vector<std::string>());
[email protected]abd4cb22014-05-16 05:22:56101 std::vector<std::string>* hashes = &(data_[relative_path].second);
102
Sungguk Limf70d1232019-06-26 20:18:11103 for (const base::Value& value : hashes_list) {
104 if (!value.is_string())
[email protected]abd4cb22014-05-16 05:22:56105 return false;
106
107 hashes->push_back(std::string());
Sungguk Limf70d1232019-06-26 20:18:11108 const std::string& encoded = value.GetString();
[email protected]abd4cb22014-05-16 05:22:56109 std::string* decoded = &hashes->back();
110 if (!base::Base64Decode(encoded, decoded)) {
111 hashes->clear();
112 return false;
113 }
114 }
115 }
Istiaque Ahmed588df562018-07-06 20:16:04116 uma_recorder.RecordSuccess();
[email protected]abd4cb22014-05-16 05:22:56117 return true;
118}
119
120bool ComputedHashes::Reader::GetHashes(const base::FilePath& relative_path,
121 int* block_size,
Istiaque Ahmed9bdd9d92017-12-16 04:53:27122 std::vector<std::string>* hashes) const {
[email protected]4f9bdf62014-06-28 01:08:22123 base::FilePath path = relative_path.NormalizePathSeparatorsTo('/');
jdoerriea1e1598b2018-10-10 09:10:37124 auto i = data_.find(path);
asargent793691912014-10-04 01:12:21125 if (i == data_.end()) {
126 // If we didn't find the entry using exact match, it's possible the
127 // developer is using a path with some letters in the incorrect case, which
128 // happens to work on windows/osx. So try doing a linear scan to look for a
129 // case-insensitive match. In practice most extensions don't have that big
130 // a list of files so the performance penalty is probably not too big
131 // here. Also for crbug.com/29941 we plan to start warning developers when
132 // they are making this mistake, since their extension will be broken on
133 // linux/chromeos.
134 for (i = data_.begin(); i != data_.end(); ++i) {
135 const base::FilePath& entry = i->first;
136 if (base::FilePath::CompareEqualIgnoreCase(entry.value(), path.value()))
137 break;
138 }
139 if (i == data_.end())
140 return false;
141 }
Istiaque Ahmed9bdd9d92017-12-16 04:53:27142 const HashInfo& info = i->second;
[email protected]abd4cb22014-05-16 05:22:56143 *block_size = info.first;
144 *hashes = info.second;
145 return true;
146}
147
[email protected]de00aeb2014-08-06 09:13:39148ComputedHashes::Writer::Writer() : file_list_(new base::ListValue) {
[email protected]abd4cb22014-05-16 05:22:56149}
[email protected]de00aeb2014-08-06 09:13:39150
[email protected]abd4cb22014-05-16 05:22:56151ComputedHashes::Writer::~Writer() {
152}
153
154void ComputedHashes::Writer::AddHashes(const base::FilePath& relative_path,
155 int block_size,
156 const std::vector<std::string>& hashes) {
Jeremy Roman16529d0e2017-08-24 18:13:47157 auto block_hashes = std::make_unique<base::ListValue>();
jdoerrie6ff270ca2017-06-07 10:31:45158 block_hashes->GetList().reserve(hashes.size());
159 for (const auto& hash : hashes) {
160 std::string encoded;
161 base::Base64Encode(hash, &encoded);
Jan Wilken Dörrie85a66712019-09-11 18:35:09162 block_hashes->Append(std::move(encoded));
jdoerrie6ff270ca2017-06-07 10:31:45163 }
164
Jeremy Roman16529d0e2017-08-24 18:13:47165 auto dict = std::make_unique<base::DictionaryValue>();
Jens Widellfa216f12018-02-02 08:48:35166 dict->SetString(computed_hashes::kPathKey,
[email protected]4f9bdf62014-06-28 01:08:22167 relative_path.NormalizePathSeparatorsTo('/').AsUTF8Unsafe());
Jens Widellfa216f12018-02-02 08:48:35168 dict->SetInteger(computed_hashes::kBlockSizeKey, block_size);
169 dict->Set(computed_hashes::kBlockHashesKey, std::move(block_hashes));
dchengf19502002016-09-14 15:18:18170 file_list_->Append(std::move(dict));
[email protected]abd4cb22014-05-16 05:22:56171}
172
173bool ComputedHashes::Writer::WriteToFile(const base::FilePath& path) {
174 std::string json;
[email protected]de00aeb2014-08-06 09:13:39175 base::DictionaryValue top_dictionary;
Jens Widellfa216f12018-02-02 08:48:35176 top_dictionary.SetInteger(computed_hashes::kVersionKey,
177 computed_hashes::kVersion);
178 top_dictionary.Set(computed_hashes::kFileHashesKey, std::move(file_list_));
[email protected]de00aeb2014-08-06 09:13:39179
estade8d046462015-05-16 01:02:34180 if (!base::JSONWriter::Write(top_dictionary, &json))
[email protected]abd4cb22014-05-16 05:22:56181 return false;
182 int written = base::WriteFile(path, json.data(), json.size());
183 if (static_cast<unsigned>(written) != json.size()) {
[email protected]de00aeb2014-08-06 09:13:39184 LOG(ERROR) << "Error writing " << path.AsUTF8Unsafe()
[email protected]abd4cb22014-05-16 05:22:56185 << " ; write result:" << written << " expected:" << json.size();
186 return false;
187 }
188 return true;
189}
190
[email protected]de00aeb2014-08-06 09:13:39191void ComputedHashes::ComputeHashesForContent(const std::string& contents,
192 size_t block_size,
193 std::vector<std::string>* hashes) {
194 size_t offset = 0;
195 // Even when the contents is empty, we want to output at least one hash
196 // block (the hash of the empty string).
197 do {
198 const char* block_start = contents.data() + offset;
199 DCHECK(offset <= contents.size());
200 size_t bytes_to_read = std::min(contents.size() - offset, block_size);
dchengf5d241082016-04-21 03:43:11201 std::unique_ptr<crypto::SecureHash> hash(
[email protected]de00aeb2014-08-06 09:13:39202 crypto::SecureHash::Create(crypto::SecureHash::SHA256));
203 hash->Update(block_start, bytes_to_read);
204
205 hashes->push_back(std::string());
206 std::string* buffer = &(hashes->back());
207 buffer->resize(crypto::kSHA256Length);
Ryan Sleevi972b2ff2018-05-14 15:45:10208 hash->Finish(base::data(*buffer), buffer->size());
[email protected]de00aeb2014-08-06 09:13:39209
210 // If |contents| is empty, then we want to just exit here.
211 if (bytes_to_read == 0)
212 break;
213
214 offset += bytes_to_read;
215 } while (offset < contents.size());
216}
217
[email protected]abd4cb22014-05-16 05:22:56218} // namespace extensions