1 // Copyright 2013 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.
5 #include "chrome/browser/prefs/pref_hash_store_impl.h"
7 #include "base/logging.h"
8 #include "base/metrics/histogram.h"
9 #include "base/values.h"
10 #include "chrome/browser/prefs/pref_hash_store_transaction.h"
11 #include "chrome/browser/prefs/tracked/hash_store_contents.h"
15 // Returns true if the dictionary of hashes stored in |contents| is trusted
16 // (which implies unknown values can be trusted as newly tracked values).
17 bool IsHashDictionaryTrusted(const PrefHashCalculator
& calculator
,
18 const HashStoreContents
& contents
) {
19 const base::DictionaryValue
* store_contents
= contents
.GetContents();
20 std::string super_mac
= contents
.GetSuperMac();
21 // The store must be initialized and have a valid super MAC to be trusted.
22 return store_contents
&& !super_mac
.empty() &&
23 calculator
.Validate(contents
.hash_store_id(),
25 super_mac
) == PrefHashCalculator::VALID
;
30 class PrefHashStoreImpl::PrefHashStoreTransactionImpl
31 : public PrefHashStoreTransaction
{
33 // Constructs a PrefHashStoreTransactionImpl which can use the private
34 // members of its |outer| PrefHashStoreImpl.
35 explicit PrefHashStoreTransactionImpl(PrefHashStoreImpl
* outer
);
36 virtual ~PrefHashStoreTransactionImpl();
38 // PrefHashStoreTransaction implementation.
39 virtual ValueState
CheckValue(const std::string
& path
,
40 const base::Value
* value
) const OVERRIDE
;
41 virtual void StoreHash(const std::string
& path
,
42 const base::Value
* value
) OVERRIDE
;
43 virtual ValueState
CheckSplitValue(
44 const std::string
& path
,
45 const base::DictionaryValue
* initial_split_value
,
46 std::vector
<std::string
>* invalid_keys
) const OVERRIDE
;
47 virtual void StoreSplitHash(
48 const std::string
& path
,
49 const base::DictionaryValue
* split_value
) OVERRIDE
;
52 bool GetSplitMacs(const std::string
& path
,
53 std::map
<std::string
, std::string
>* split_macs
) const;
54 PrefHashStoreImpl
* outer_
;
57 DISALLOW_COPY_AND_ASSIGN(PrefHashStoreTransactionImpl
);
60 PrefHashStoreImpl::PrefHashStoreImpl(const std::string
& seed
,
61 const std::string
& device_id
,
62 scoped_ptr
<HashStoreContents
> contents
)
63 : pref_hash_calculator_(seed
, device_id
),
64 contents_(contents
.Pass()),
65 initial_hashes_dictionary_trusted_(
66 IsHashDictionaryTrusted(pref_hash_calculator_
, *contents_
)),
67 has_pending_write_(false) {
69 UMA_HISTOGRAM_BOOLEAN("Settings.HashesDictionaryTrusted",
70 initial_hashes_dictionary_trusted_
);
73 PrefHashStoreImpl::~PrefHashStoreImpl() {}
75 void PrefHashStoreImpl::Reset() {
79 scoped_ptr
<PrefHashStoreTransaction
> PrefHashStoreImpl::BeginTransaction() {
80 return scoped_ptr
<PrefHashStoreTransaction
>(
81 new PrefHashStoreTransactionImpl(this));
84 PrefHashStoreImpl::StoreVersion
PrefHashStoreImpl::GetCurrentVersion() const {
85 if (!contents_
->IsInitialized())
86 return VERSION_UNINITIALIZED
;
89 if (!contents_
->GetVersion(¤t_version
)) {
90 return VERSION_PRE_MIGRATION
;
93 DCHECK_GT(current_version
, VERSION_PRE_MIGRATION
);
94 return static_cast<StoreVersion
>(current_version
);
97 void PrefHashStoreImpl::CommitPendingWrite() {
98 if (has_pending_write_
) {
99 contents_
->CommitPendingWrite();
100 has_pending_write_
= false;
104 PrefHashStoreImpl::PrefHashStoreTransactionImpl::PrefHashStoreTransactionImpl(
105 PrefHashStoreImpl
* outer
) : outer_(outer
), has_changed_(false) {
108 PrefHashStoreImpl::PrefHashStoreTransactionImpl::
109 ~PrefHashStoreTransactionImpl() {
110 // Update the super MAC if and only if the hashes dictionary has been
111 // modified in this transaction.
113 // Get the dictionary of hashes (or NULL if it doesn't exist).
114 const base::DictionaryValue
* hashes_dict
= outer_
->contents_
->GetContents();
115 outer_
->contents_
->SetSuperMac(outer_
->pref_hash_calculator_
.Calculate(
116 outer_
->contents_
->hash_store_id(), hashes_dict
));
118 outer_
->has_pending_write_
= true;
121 // Mark this hash store has having been updated to the latest version (in
122 // practice only initialization transactions will actually do this, but
123 // since they always occur before minor update transaction it's okay
124 // to unconditionally do this here). Only do this if this store's version
125 // isn't already at VERSION_LATEST (to avoid scheduling a write when
126 // unecessary). Note, this is outside of |if (has_changed)| to also seed
127 // version number of otherwise unchanged profiles.
129 if (!outer_
->contents_
->GetVersion(¤t_version
) ||
130 current_version
!= VERSION_LATEST
) {
131 outer_
->contents_
->SetVersion(VERSION_LATEST
);
132 outer_
->has_pending_write_
= true;
136 PrefHashStoreTransaction::ValueState
137 PrefHashStoreImpl::PrefHashStoreTransactionImpl::CheckValue(
138 const std::string
& path
, const base::Value
* initial_value
) const {
139 const base::DictionaryValue
* hashed_prefs
= outer_
->contents_
->GetContents();
141 std::string last_hash
;
143 hashed_prefs
->GetString(path
, &last_hash
);
145 if (last_hash
.empty()) {
146 // In the absence of a hash for this pref, always trust a NULL value, but
147 // only trust an existing value if the initial hashes dictionary is trusted.
148 return (!initial_value
|| outer_
->initial_hashes_dictionary_trusted_
) ?
149 TRUSTED_UNKNOWN_VALUE
: UNTRUSTED_UNKNOWN_VALUE
;
152 PrefHashCalculator::ValidationResult validation_result
=
153 outer_
->pref_hash_calculator_
.Validate(path
, initial_value
, last_hash
);
154 switch (validation_result
) {
155 case PrefHashCalculator::VALID
:
157 case PrefHashCalculator::VALID_WEAK_LEGACY
:
159 case PrefHashCalculator::VALID_SECURE_LEGACY
:
160 return SECURE_LEGACY
;
161 case PrefHashCalculator::INVALID
:
162 return initial_value
? CHANGED
: CLEARED
;
164 NOTREACHED() << "Unexpected PrefHashCalculator::ValidationResult: "
165 << validation_result
;
166 return UNTRUSTED_UNKNOWN_VALUE
;
169 void PrefHashStoreImpl::PrefHashStoreTransactionImpl::StoreHash(
170 const std::string
& path
, const base::Value
* new_value
) {
171 const std::string mac
=
172 outer_
->pref_hash_calculator_
.Calculate(path
, new_value
);
173 (*outer_
->contents_
->GetMutableContents())->SetString(path
, mac
);
177 PrefHashStoreTransaction::ValueState
178 PrefHashStoreImpl::PrefHashStoreTransactionImpl::CheckSplitValue(
179 const std::string
& path
,
180 const base::DictionaryValue
* initial_split_value
,
181 std::vector
<std::string
>* invalid_keys
) const {
182 DCHECK(invalid_keys
&& invalid_keys
->empty());
184 std::map
<std::string
, std::string
> split_macs
;
185 const bool has_hashes
= GetSplitMacs(path
, &split_macs
);
187 // Treat NULL and empty the same; otherwise we would need to store a hash
188 // for the entire dictionary (or some other special beacon) to
189 // differentiate these two cases which are really the same for
191 if (!initial_split_value
|| initial_split_value
->empty())
192 return has_hashes
? CLEARED
: UNCHANGED
;
195 return outer_
->initial_hashes_dictionary_trusted_
?
196 TRUSTED_UNKNOWN_VALUE
: UNTRUSTED_UNKNOWN_VALUE
;
199 bool has_secure_legacy_id_hashes
= false;
200 std::string
keyed_path(path
);
201 keyed_path
.push_back('.');
202 const size_t common_part_length
= keyed_path
.length();
203 for (base::DictionaryValue::Iterator
it(*initial_split_value
); !it
.IsAtEnd();
205 std::map
<std::string
, std::string
>::iterator entry
=
206 split_macs
.find(it
.key());
207 if (entry
== split_macs
.end()) {
208 invalid_keys
->push_back(it
.key());
210 // Keep the common part from the old |keyed_path| and replace the key to
211 // get the new |keyed_path|.
212 keyed_path
.replace(common_part_length
, std::string::npos
, it
.key());
213 switch (outer_
->pref_hash_calculator_
.Validate(
214 keyed_path
, &it
.value(), entry
->second
)) {
215 case PrefHashCalculator::VALID
:
218 // Split tracked preferences were introduced after the migration from
219 // the weaker legacy algorithm started so no migration is expected,
220 // but declare it invalid in Release builds anyways.
222 invalid_keys
->push_back(it
.key());
225 // Secure legacy device IDs based hashes are still accepted, but we
226 // should make sure to notify the caller for him to update the legacy
228 has_secure_legacy_id_hashes
= true;
230 case PrefHashCalculator::INVALID
:
231 invalid_keys
->push_back(it
.key());
234 // Remove processed MACs, remaining MACs at the end will also be
235 // considered invalid.
236 split_macs
.erase(entry
);
240 // Anything left in the map is missing from the data.
241 for (std::map
<std::string
, std::string
>::const_iterator it
=
243 it
!= split_macs
.end();
245 invalid_keys
->push_back(it
->first
);
248 return invalid_keys
->empty()
249 ? (has_secure_legacy_id_hashes
? SECURE_LEGACY
: UNCHANGED
)
253 void PrefHashStoreImpl::PrefHashStoreTransactionImpl::StoreSplitHash(
254 const std::string
& path
,
255 const base::DictionaryValue
* split_value
) {
256 scoped_ptr
<HashStoreContents::MutableDictionary
> mutable_dictionary
=
257 outer_
->contents_
->GetMutableContents();
258 (*mutable_dictionary
)->Remove(path
, NULL
);
261 std::string
keyed_path(path
);
262 keyed_path
.push_back('.');
263 const size_t common_part_length
= keyed_path
.length();
264 for (base::DictionaryValue::Iterator
it(*split_value
); !it
.IsAtEnd();
266 // Keep the common part from the old |keyed_path| and replace the key to
267 // get the new |keyed_path|.
268 keyed_path
.replace(common_part_length
, std::string::npos
, it
.key());
269 (*mutable_dictionary
)->SetString(
271 outer_
->pref_hash_calculator_
.Calculate(keyed_path
, &it
.value()));
277 bool PrefHashStoreImpl::PrefHashStoreTransactionImpl::GetSplitMacs(
278 const std::string
& key
,
279 std::map
<std::string
, std::string
>* split_macs
) const {
281 DCHECK(split_macs
->empty());
283 const base::DictionaryValue
* hashed_prefs
= outer_
->contents_
->GetContents();
284 const base::DictionaryValue
* split_mac_dictionary
= NULL
;
285 if (!hashed_prefs
|| !hashed_prefs
->GetDictionary(key
, &split_mac_dictionary
))
287 for (base::DictionaryValue::Iterator
it(*split_mac_dictionary
); !it
.IsAtEnd();
289 std::string mac_string
;
290 if (!it
.value().GetAsString(&mac_string
)) {
294 split_macs
->insert(make_pair(it
.key(), mac_string
));