blob: cfe0a9e84e898287bee6c7016dd9f5a75fa55bc9 [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"
16#include "base/values.h"
17#include "crypto/secure_hash.h"
18#include "crypto/sha2.h"
[email protected]abd4cb22014-05-16 05:22:5619
Jens Widellfa216f12018-02-02 08:48:3520namespace extensions {
21
22namespace computed_hashes {
[email protected]abd4cb22014-05-16 05:22:5623const char kBlockHashesKey[] = "block_hashes";
[email protected]de00aeb2014-08-06 09:13:3924const char kBlockSizeKey[] = "block_size";
25const char kFileHashesKey[] = "file_hashes";
26const char kPathKey[] = "path";
27const char kVersionKey[] = "version";
28const int kVersion = 2;
Jens Widellfa216f12018-02-02 08:48:3529} // namespace computed_hashes
[email protected]abd4cb22014-05-16 05:22:5630
31ComputedHashes::Reader::Reader() {
32}
[email protected]de00aeb2014-08-06 09:13:3933
[email protected]abd4cb22014-05-16 05:22:5634ComputedHashes::Reader::~Reader() {
35}
36
37bool ComputedHashes::Reader::InitFromFile(const base::FilePath& path) {
38 std::string contents;
39 if (!base::ReadFileToString(path, &contents))
40 return false;
41
[email protected]de00aeb2014-08-06 09:13:3942 base::DictionaryValue* top_dictionary = NULL;
dchengf5d241082016-04-21 03:43:1143 std::unique_ptr<base::Value> value(base::JSONReader::Read(contents));
[email protected]de00aeb2014-08-06 09:13:3944 if (!value.get() || !value->GetAsDictionary(&top_dictionary))
45 return false;
46
47 // For now we don't support forwards or backwards compatability in the
48 // format, so we return false on version mismatch.
49 int version = 0;
Jens Widellfa216f12018-02-02 08:48:3550 if (!top_dictionary->GetInteger(computed_hashes::kVersionKey, &version) ||
51 version != computed_hashes::kVersion)
[email protected]de00aeb2014-08-06 09:13:3952 return false;
53
54 base::ListValue* all_hashes = NULL;
Jens Widellfa216f12018-02-02 08:48:3555 if (!top_dictionary->GetList(computed_hashes::kFileHashesKey, &all_hashes))
[email protected]abd4cb22014-05-16 05:22:5656 return false;
57
58 for (size_t i = 0; i < all_hashes->GetSize(); i++) {
59 base::DictionaryValue* dictionary = NULL;
60 if (!all_hashes->GetDictionary(i, &dictionary))
61 return false;
62
63 std::string relative_path_utf8;
Jens Widellfa216f12018-02-02 08:48:3564 if (!dictionary->GetString(computed_hashes::kPathKey, &relative_path_utf8))
[email protected]abd4cb22014-05-16 05:22:5665 return false;
66
67 int block_size;
Jens Widellfa216f12018-02-02 08:48:3568 if (!dictionary->GetInteger(computed_hashes::kBlockSizeKey, &block_size))
[email protected]abd4cb22014-05-16 05:22:5669 return false;
70 if (block_size <= 0 || ((block_size % 1024) != 0)) {
71 LOG(ERROR) << "Invalid block size: " << block_size;
[email protected]abd4cb22014-05-16 05:22:5672 return false;
73 }
74
75 base::ListValue* hashes_list = NULL;
Jens Widellfa216f12018-02-02 08:48:3576 if (!dictionary->GetList(computed_hashes::kBlockHashesKey, &hashes_list))
[email protected]abd4cb22014-05-16 05:22:5677 return false;
78
79 base::FilePath relative_path =
80 base::FilePath::FromUTF8Unsafe(relative_path_utf8);
[email protected]4f9bdf62014-06-28 01:08:2281 relative_path = relative_path.NormalizePathSeparatorsTo('/');
[email protected]abd4cb22014-05-16 05:22:5682
83 data_[relative_path] = HashInfo(block_size, std::vector<std::string>());
84 std::vector<std::string>* hashes = &(data_[relative_path].second);
85
86 for (size_t j = 0; j < hashes_list->GetSize(); j++) {
87 std::string encoded;
88 if (!hashes_list->GetString(j, &encoded))
89 return false;
90
91 hashes->push_back(std::string());
92 std::string* decoded = &hashes->back();
93 if (!base::Base64Decode(encoded, decoded)) {
94 hashes->clear();
95 return false;
96 }
97 }
98 }
99 return true;
100}
101
102bool ComputedHashes::Reader::GetHashes(const base::FilePath& relative_path,
103 int* block_size,
Istiaque Ahmed9bdd9d92017-12-16 04:53:27104 std::vector<std::string>* hashes) const {
[email protected]4f9bdf62014-06-28 01:08:22105 base::FilePath path = relative_path.NormalizePathSeparatorsTo('/');
Istiaque Ahmed9bdd9d92017-12-16 04:53:27106 std::map<base::FilePath, HashInfo>::const_iterator i = data_.find(path);
asargent793691912014-10-04 01:12:21107 if (i == data_.end()) {
108 // If we didn't find the entry using exact match, it's possible the
109 // developer is using a path with some letters in the incorrect case, which
110 // happens to work on windows/osx. So try doing a linear scan to look for a
111 // case-insensitive match. In practice most extensions don't have that big
112 // a list of files so the performance penalty is probably not too big
113 // here. Also for crbug.com/29941 we plan to start warning developers when
114 // they are making this mistake, since their extension will be broken on
115 // linux/chromeos.
116 for (i = data_.begin(); i != data_.end(); ++i) {
117 const base::FilePath& entry = i->first;
118 if (base::FilePath::CompareEqualIgnoreCase(entry.value(), path.value()))
119 break;
120 }
121 if (i == data_.end())
122 return false;
123 }
Istiaque Ahmed9bdd9d92017-12-16 04:53:27124 const HashInfo& info = i->second;
[email protected]abd4cb22014-05-16 05:22:56125 *block_size = info.first;
126 *hashes = info.second;
127 return true;
128}
129
[email protected]de00aeb2014-08-06 09:13:39130ComputedHashes::Writer::Writer() : file_list_(new base::ListValue) {
[email protected]abd4cb22014-05-16 05:22:56131}
[email protected]de00aeb2014-08-06 09:13:39132
[email protected]abd4cb22014-05-16 05:22:56133ComputedHashes::Writer::~Writer() {
134}
135
136void ComputedHashes::Writer::AddHashes(const base::FilePath& relative_path,
137 int block_size,
138 const std::vector<std::string>& hashes) {
Jeremy Roman16529d0e2017-08-24 18:13:47139 auto block_hashes = std::make_unique<base::ListValue>();
jdoerrie6ff270ca2017-06-07 10:31:45140 block_hashes->GetList().reserve(hashes.size());
141 for (const auto& hash : hashes) {
142 std::string encoded;
143 base::Base64Encode(hash, &encoded);
144 block_hashes->GetList().emplace_back(std::move(encoded));
145 }
146
Jeremy Roman16529d0e2017-08-24 18:13:47147 auto dict = std::make_unique<base::DictionaryValue>();
Jens Widellfa216f12018-02-02 08:48:35148 dict->SetString(computed_hashes::kPathKey,
[email protected]4f9bdf62014-06-28 01:08:22149 relative_path.NormalizePathSeparatorsTo('/').AsUTF8Unsafe());
Jens Widellfa216f12018-02-02 08:48:35150 dict->SetInteger(computed_hashes::kBlockSizeKey, block_size);
151 dict->Set(computed_hashes::kBlockHashesKey, std::move(block_hashes));
dchengf19502002016-09-14 15:18:18152 file_list_->Append(std::move(dict));
[email protected]abd4cb22014-05-16 05:22:56153}
154
155bool ComputedHashes::Writer::WriteToFile(const base::FilePath& path) {
156 std::string json;
[email protected]de00aeb2014-08-06 09:13:39157 base::DictionaryValue top_dictionary;
Jens Widellfa216f12018-02-02 08:48:35158 top_dictionary.SetInteger(computed_hashes::kVersionKey,
159 computed_hashes::kVersion);
160 top_dictionary.Set(computed_hashes::kFileHashesKey, std::move(file_list_));
[email protected]de00aeb2014-08-06 09:13:39161
estade8d046462015-05-16 01:02:34162 if (!base::JSONWriter::Write(top_dictionary, &json))
[email protected]abd4cb22014-05-16 05:22:56163 return false;
164 int written = base::WriteFile(path, json.data(), json.size());
165 if (static_cast<unsigned>(written) != json.size()) {
[email protected]de00aeb2014-08-06 09:13:39166 LOG(ERROR) << "Error writing " << path.AsUTF8Unsafe()
[email protected]abd4cb22014-05-16 05:22:56167 << " ; write result:" << written << " expected:" << json.size();
168 return false;
169 }
170 return true;
171}
172
[email protected]de00aeb2014-08-06 09:13:39173void ComputedHashes::ComputeHashesForContent(const std::string& contents,
174 size_t block_size,
175 std::vector<std::string>* hashes) {
176 size_t offset = 0;
177 // Even when the contents is empty, we want to output at least one hash
178 // block (the hash of the empty string).
179 do {
180 const char* block_start = contents.data() + offset;
181 DCHECK(offset <= contents.size());
182 size_t bytes_to_read = std::min(contents.size() - offset, block_size);
dchengf5d241082016-04-21 03:43:11183 std::unique_ptr<crypto::SecureHash> hash(
[email protected]de00aeb2014-08-06 09:13:39184 crypto::SecureHash::Create(crypto::SecureHash::SHA256));
185 hash->Update(block_start, bytes_to_read);
186
187 hashes->push_back(std::string());
188 std::string* buffer = &(hashes->back());
189 buffer->resize(crypto::kSHA256Length);
Ryan Sleevi972b2ff2018-05-14 15:45:10190 hash->Finish(base::data(*buffer), buffer->size());
[email protected]de00aeb2014-08-06 09:13:39191
192 // If |contents| is empty, then we want to just exit here.
193 if (bytes_to_read == 0)
194 break;
195
196 offset += bytes_to_read;
197 } while (offset < contents.size());
198}
199
[email protected]abd4cb22014-05-16 05:22:56200} // namespace extensions