blob: 0a196a807982347d2134c8eb3f975a0072e11cda [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"
jdoerrie6ff270ca2017-06-07 10:31:4515#include "base/memory/ptr_util.h"
[email protected]de00aeb2014-08-06 09:13:3916#include "base/stl_util.h"
17#include "base/values.h"
18#include "crypto/secure_hash.h"
19#include "crypto/sha2.h"
[email protected]abd4cb22014-05-16 05:22:5620
21namespace {
[email protected]abd4cb22014-05-16 05:22:5622const char kBlockHashesKey[] = "block_hashes";
[email protected]de00aeb2014-08-06 09:13:3923const char kBlockSizeKey[] = "block_size";
24const char kFileHashesKey[] = "file_hashes";
25const char kPathKey[] = "path";
26const char kVersionKey[] = "version";
27const int kVersion = 2;
28} // namespace
[email protected]abd4cb22014-05-16 05:22:5629
30namespace extensions {
31
32ComputedHashes::Reader::Reader() {
33}
[email protected]de00aeb2014-08-06 09:13:3934
[email protected]abd4cb22014-05-16 05:22:5635ComputedHashes::Reader::~Reader() {
36}
37
38bool ComputedHashes::Reader::InitFromFile(const base::FilePath& path) {
39 std::string contents;
40 if (!base::ReadFileToString(path, &contents))
41 return false;
42
[email protected]de00aeb2014-08-06 09:13:3943 base::DictionaryValue* top_dictionary = NULL;
dchengf5d241082016-04-21 03:43:1144 std::unique_ptr<base::Value> value(base::JSONReader::Read(contents));
[email protected]de00aeb2014-08-06 09:13:3945 if (!value.get() || !value->GetAsDictionary(&top_dictionary))
46 return false;
47
48 // For now we don't support forwards or backwards compatability in the
49 // format, so we return false on version mismatch.
50 int version = 0;
51 if (!top_dictionary->GetInteger(kVersionKey, &version) || version != kVersion)
52 return false;
53
54 base::ListValue* all_hashes = NULL;
55 if (!top_dictionary->GetList(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;
64 if (!dictionary->GetString(kPathKey, &relative_path_utf8))
65 return false;
66
67 int block_size;
68 if (!dictionary->GetInteger(kBlockSizeKey, &block_size))
69 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;
76 if (!dictionary->GetList(kBlockHashesKey, &hashes_list))
77 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>();
[email protected]4f9bdf62014-06-28 01:08:22148 dict->SetString(kPathKey,
149 relative_path.NormalizePathSeparatorsTo('/').AsUTF8Unsafe());
[email protected]abd4cb22014-05-16 05:22:56150 dict->SetInteger(kBlockSizeKey, block_size);
jdoerrie6ff270ca2017-06-07 10:31:45151 dict->Set(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;
158 top_dictionary.SetInteger(kVersionKey, kVersion);
jdoerrie6ff270ca2017-06-07 10:31:45159 top_dictionary.Set(kFileHashesKey, std::move(file_list_));
[email protected]de00aeb2014-08-06 09:13:39160
estade8d046462015-05-16 01:02:34161 if (!base::JSONWriter::Write(top_dictionary, &json))
[email protected]abd4cb22014-05-16 05:22:56162 return false;
163 int written = base::WriteFile(path, json.data(), json.size());
164 if (static_cast<unsigned>(written) != json.size()) {
[email protected]de00aeb2014-08-06 09:13:39165 LOG(ERROR) << "Error writing " << path.AsUTF8Unsafe()
[email protected]abd4cb22014-05-16 05:22:56166 << " ; write result:" << written << " expected:" << json.size();
167 return false;
168 }
169 return true;
170}
171
[email protected]de00aeb2014-08-06 09:13:39172void ComputedHashes::ComputeHashesForContent(const std::string& contents,
173 size_t block_size,
174 std::vector<std::string>* hashes) {
175 size_t offset = 0;
176 // Even when the contents is empty, we want to output at least one hash
177 // block (the hash of the empty string).
178 do {
179 const char* block_start = contents.data() + offset;
180 DCHECK(offset <= contents.size());
181 size_t bytes_to_read = std::min(contents.size() - offset, block_size);
dchengf5d241082016-04-21 03:43:11182 std::unique_ptr<crypto::SecureHash> hash(
[email protected]de00aeb2014-08-06 09:13:39183 crypto::SecureHash::Create(crypto::SecureHash::SHA256));
184 hash->Update(block_start, bytes_to_read);
185
186 hashes->push_back(std::string());
187 std::string* buffer = &(hashes->back());
188 buffer->resize(crypto::kSHA256Length);
skyostil504e5022016-08-12 13:03:59189 hash->Finish(base::string_as_array(buffer), buffer->size());
[email protected]de00aeb2014-08-06 09:13:39190
191 // If |contents| is empty, then we want to just exit here.
192 if (bytes_to_read == 0)
193 break;
194
195 offset += bytes_to_read;
196 } while (offset < contents.size());
197}
198
[email protected]abd4cb22014-05-16 05:22:56199} // namespace extensions