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/computed_hashes.h"
6
7#include "base/base64.h"
8#include "base/files/file_path.h"
9#include "base/files/file_util.h"
10#include "base/json/json_reader.h"
11#include "base/json/json_writer.h"
12#include "base/stl_util.h"
13#include "base/values.h"
14#include "crypto/secure_hash.h"
15#include "crypto/sha2.h"
16
17namespace {
18const char kBlockHashesKey[] = "block_hashes";
19const char kBlockSizeKey[] = "block_size";
20const char kFileHashesKey[] = "file_hashes";
21const char kPathKey[] = "path";
22const char kVersionKey[] = "version";
23const int kVersion = 2;
24}  // namespace
25
26namespace extensions {
27
28ComputedHashes::Reader::Reader() {
29}
30
31ComputedHashes::Reader::~Reader() {
32}
33
34bool ComputedHashes::Reader::InitFromFile(const base::FilePath& path) {
35  std::string contents;
36  if (!base::ReadFileToString(path, &contents))
37    return false;
38
39  base::DictionaryValue* top_dictionary = NULL;
40  scoped_ptr<base::Value> value(base::JSONReader::Read(contents));
41  if (!value.get() || !value->GetAsDictionary(&top_dictionary))
42    return false;
43
44  // For now we don't support forwards or backwards compatability in the
45  // format, so we return false on version mismatch.
46  int version = 0;
47  if (!top_dictionary->GetInteger(kVersionKey, &version) || version != kVersion)
48    return false;
49
50  base::ListValue* all_hashes = NULL;
51  if (!top_dictionary->GetList(kFileHashesKey, &all_hashes))
52    return false;
53
54  for (size_t i = 0; i < all_hashes->GetSize(); i++) {
55    base::DictionaryValue* dictionary = NULL;
56    if (!all_hashes->GetDictionary(i, &dictionary))
57      return false;
58
59    std::string relative_path_utf8;
60    if (!dictionary->GetString(kPathKey, &relative_path_utf8))
61      return false;
62
63    int block_size;
64    if (!dictionary->GetInteger(kBlockSizeKey, &block_size))
65      return false;
66    if (block_size <= 0 || ((block_size % 1024) != 0)) {
67      LOG(ERROR) << "Invalid block size: " << block_size;
68      block_size = 0;
69      return false;
70    }
71
72    base::ListValue* hashes_list = NULL;
73    if (!dictionary->GetList(kBlockHashesKey, &hashes_list))
74      return false;
75
76    base::FilePath relative_path =
77        base::FilePath::FromUTF8Unsafe(relative_path_utf8);
78    relative_path = relative_path.NormalizePathSeparatorsTo('/');
79
80    data_[relative_path] = HashInfo(block_size, std::vector<std::string>());
81    std::vector<std::string>* hashes = &(data_[relative_path].second);
82
83    for (size_t j = 0; j < hashes_list->GetSize(); j++) {
84      std::string encoded;
85      if (!hashes_list->GetString(j, &encoded))
86        return false;
87
88      hashes->push_back(std::string());
89      std::string* decoded = &hashes->back();
90      if (!base::Base64Decode(encoded, decoded)) {
91        hashes->clear();
92        return false;
93      }
94    }
95  }
96  return true;
97}
98
99bool ComputedHashes::Reader::GetHashes(const base::FilePath& relative_path,
100                                       int* block_size,
101                                       std::vector<std::string>* hashes) {
102  base::FilePath path = relative_path.NormalizePathSeparatorsTo('/');
103  std::map<base::FilePath, HashInfo>::iterator i = data_.find(path);
104  if (i == data_.end()) {
105    // If we didn't find the entry using exact match, it's possible the
106    // developer is using a path with some letters in the incorrect case, which
107    // happens to work on windows/osx. So try doing a linear scan to look for a
108    // case-insensitive match. In practice most extensions don't have that big
109    // a list of files so the performance penalty is probably not too big
110    // here. Also for crbug.com/29941 we plan to start warning developers when
111    // they are making this mistake, since their extension will be broken on
112    // linux/chromeos.
113    for (i = data_.begin(); i != data_.end(); ++i) {
114      const base::FilePath& entry = i->first;
115      if (base::FilePath::CompareEqualIgnoreCase(entry.value(), path.value()))
116        break;
117    }
118    if (i == data_.end())
119      return false;
120  }
121  HashInfo& info = i->second;
122  *block_size = info.first;
123  *hashes = info.second;
124  return true;
125}
126
127ComputedHashes::Writer::Writer() : file_list_(new base::ListValue) {
128}
129
130ComputedHashes::Writer::~Writer() {
131}
132
133void ComputedHashes::Writer::AddHashes(const base::FilePath& relative_path,
134                                       int block_size,
135                                       const std::vector<std::string>& hashes) {
136  base::DictionaryValue* dict = new base::DictionaryValue();
137  base::ListValue* block_hashes = new base::ListValue();
138  file_list_->Append(dict);
139  dict->SetString(kPathKey,
140                  relative_path.NormalizePathSeparatorsTo('/').AsUTF8Unsafe());
141  dict->SetInteger(kBlockSizeKey, block_size);
142  dict->Set(kBlockHashesKey, block_hashes);
143
144  for (std::vector<std::string>::const_iterator i = hashes.begin();
145       i != hashes.end();
146       ++i) {
147    std::string encoded;
148    base::Base64Encode(*i, &encoded);
149    block_hashes->AppendString(encoded);
150  }
151}
152
153bool ComputedHashes::Writer::WriteToFile(const base::FilePath& path) {
154  std::string json;
155  base::DictionaryValue top_dictionary;
156  top_dictionary.SetInteger(kVersionKey, kVersion);
157  top_dictionary.Set(kFileHashesKey, file_list_.release());
158
159  if (!base::JSONWriter::Write(&top_dictionary, &json))
160    return false;
161  int written = base::WriteFile(path, json.data(), json.size());
162  if (static_cast<unsigned>(written) != json.size()) {
163    LOG(ERROR) << "Error writing " << path.AsUTF8Unsafe()
164               << " ; write result:" << written << " expected:" << json.size();
165    return false;
166  }
167  return true;
168}
169
170void ComputedHashes::ComputeHashesForContent(const std::string& contents,
171                                             size_t block_size,
172                                             std::vector<std::string>* hashes) {
173  size_t offset = 0;
174  // Even when the contents is empty, we want to output at least one hash
175  // block (the hash of the empty string).
176  do {
177    const char* block_start = contents.data() + offset;
178    DCHECK(offset <= contents.size());
179    size_t bytes_to_read = std::min(contents.size() - offset, block_size);
180    scoped_ptr<crypto::SecureHash> hash(
181        crypto::SecureHash::Create(crypto::SecureHash::SHA256));
182    hash->Update(block_start, bytes_to_read);
183
184    hashes->push_back(std::string());
185    std::string* buffer = &(hashes->back());
186    buffer->resize(crypto::kSHA256Length);
187    hash->Finish(string_as_array(buffer), buffer->size());
188
189    // If |contents| is empty, then we want to just exit here.
190    if (bytes_to_read == 0)
191      break;
192
193    offset += bytes_to_read;
194  } while (offset < contents.size());
195}
196
197}  // namespace extensions
198