blob: bb920ca4c98fdcf7707952d7b4079be3e1b2b9bd [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/user_prefs/tracked/pref_hash_store_impl.h"
#include <stddef.h>
#include <utility>
#include "base/logging.h"
#include "base/macros.h"
#include "base/metrics/histogram_macros.h"
#include "components/user_prefs/tracked/device_id.h"
#include "components/user_prefs/tracked/hash_store_contents.h"
namespace {
// Returns a deterministic ID for this machine.
std::string GenerateDeviceId() {
static std::string cached_device_id;
if (!cached_device_id.empty())
return cached_device_id;
std::string device_id;
MachineIdStatus status = GetDeterministicMachineSpecificId(&device_id);
if (status != MachineIdStatus::NOT_IMPLEMENTED) {
// TODO(proberge): Remove this histogram once we validate that machine id
// generation is not flaky and consider adding a CHECK or DCHECK.
UMA_HISTOGRAM_BOOLEAN("Settings.MachineIdGenerationSuccess",
status == MachineIdStatus::SUCCESS);
}
if (status == MachineIdStatus::SUCCESS) {
cached_device_id = device_id;
return device_id;
}
return std::string();
}
} // namespace
class PrefHashStoreImpl::PrefHashStoreTransactionImpl
: public PrefHashStoreTransaction {
public:
// Constructs a PrefHashStoreTransactionImpl which can use the private
// members of its |outer| PrefHashStoreImpl.
PrefHashStoreTransactionImpl(PrefHashStoreImpl* outer,
HashStoreContents* storage);
~PrefHashStoreTransactionImpl() override;
// PrefHashStoreTransaction implementation.
base::StringPiece GetStoreUMASuffix() const override;
ValueState CheckValue(const std::string& path,
const base::Value* value) const override;
void StoreHash(const std::string& path, const base::Value* value) override;
ValueState CheckSplitValue(
const std::string& path,
const base::DictionaryValue* initial_split_value,
std::vector<std::string>* invalid_keys) const override;
void StoreSplitHash(const std::string& path,
const base::DictionaryValue* split_value) override;
bool HasHash(const std::string& path) const override;
void ImportHash(const std::string& path, const base::Value* hash) override;
void ClearHash(const std::string& path) override;
bool IsSuperMACValid() const override;
bool StampSuperMac() override;
private:
PrefHashStoreImpl* outer_;
HashStoreContents* contents_;
bool super_mac_valid_;
bool super_mac_dirty_;
DISALLOW_COPY_AND_ASSIGN(PrefHashStoreTransactionImpl);
};
PrefHashStoreImpl::PrefHashStoreImpl(const std::string& seed,
const std::string& legacy_device_id,
bool use_super_mac)
: pref_hash_calculator_(seed, GenerateDeviceId(), legacy_device_id),
use_super_mac_(use_super_mac) {}
PrefHashStoreImpl::~PrefHashStoreImpl() {
}
std::unique_ptr<PrefHashStoreTransaction> PrefHashStoreImpl::BeginTransaction(
HashStoreContents* storage) {
return std::unique_ptr<PrefHashStoreTransaction>(
new PrefHashStoreTransactionImpl(this, std::move(storage)));
}
std::string PrefHashStoreImpl::ComputeMac(const std::string& path,
const base::Value* value) {
return pref_hash_calculator_.Calculate(path, value);
}
std::unique_ptr<base::DictionaryValue> PrefHashStoreImpl::ComputeSplitMacs(
const std::string& path,
const base::DictionaryValue* split_values) {
DCHECK(split_values);
std::string keyed_path(path);
keyed_path.push_back('.');
const size_t common_part_length = keyed_path.length();
std::unique_ptr<base::DictionaryValue> split_macs(new base::DictionaryValue);
for (base::DictionaryValue::Iterator it(*split_values); !it.IsAtEnd();
it.Advance()) {
// Keep the common part from the old |keyed_path| and replace the key to
// get the new |keyed_path|.
keyed_path.replace(common_part_length, std::string::npos, it.key());
split_macs->SetStringWithoutPathExpansion(
it.key(), ComputeMac(keyed_path, &it.value()));
}
return split_macs;
}
PrefHashStoreImpl::PrefHashStoreTransactionImpl::PrefHashStoreTransactionImpl(
PrefHashStoreImpl* outer,
HashStoreContents* storage)
: outer_(outer),
contents_(std::move(storage)),
super_mac_valid_(false),
super_mac_dirty_(false) {
if (!outer_->use_super_mac_)
return;
// The store must have a valid super MAC to be trusted.
std::string super_mac = contents_->GetSuperMac();
if (super_mac.empty())
return;
super_mac_valid_ =
outer_->pref_hash_calculator_.Validate(
"", contents_->GetContents(), super_mac) == PrefHashCalculator::VALID;
}
PrefHashStoreImpl::PrefHashStoreTransactionImpl::
~PrefHashStoreTransactionImpl() {
if (super_mac_dirty_ && outer_->use_super_mac_) {
// Get the dictionary of hashes (or NULL if it doesn't exist).
const base::DictionaryValue* hashes_dict = contents_->GetContents();
contents_->SetSuperMac(outer_->ComputeMac("", hashes_dict));
}
}
base::StringPiece
PrefHashStoreImpl::PrefHashStoreTransactionImpl::GetStoreUMASuffix() const {
return contents_->GetUMASuffix();
}
PrefHashStoreTransaction::ValueState
PrefHashStoreImpl::PrefHashStoreTransactionImpl::CheckValue(
const std::string& path,
const base::Value* initial_value) const {
std::string last_hash;
contents_->GetMac(path, &last_hash);
if (last_hash.empty()) {
// In the absence of a hash for this pref, always trust a NULL value, but
// only trust an existing value if the initial hashes dictionary is trusted.
if (!initial_value)
return TRUSTED_NULL_VALUE;
else if (super_mac_valid_)
return TRUSTED_UNKNOWN_VALUE;
else
return UNTRUSTED_UNKNOWN_VALUE;
}
PrefHashCalculator::ValidationResult validation_result =
outer_->pref_hash_calculator_.Validate(path, initial_value, last_hash);
switch (validation_result) {
case PrefHashCalculator::VALID:
return UNCHANGED;
case PrefHashCalculator::VALID_SECURE_LEGACY:
return SECURE_LEGACY;
case PrefHashCalculator::INVALID:
return initial_value ? CHANGED : CLEARED;
}
NOTREACHED() << "Unexpected PrefHashCalculator::ValidationResult: "
<< validation_result;
return UNTRUSTED_UNKNOWN_VALUE;
}
void PrefHashStoreImpl::PrefHashStoreTransactionImpl::StoreHash(
const std::string& path,
const base::Value* new_value) {
const std::string mac = outer_->ComputeMac(path, new_value);
contents_->SetMac(path, mac);
super_mac_dirty_ = true;
}
PrefHashStoreTransaction::ValueState
PrefHashStoreImpl::PrefHashStoreTransactionImpl::CheckSplitValue(
const std::string& path,
const base::DictionaryValue* initial_split_value,
std::vector<std::string>* invalid_keys) const {
DCHECK(invalid_keys && invalid_keys->empty());
std::map<std::string, std::string> split_macs;
const bool has_hashes = contents_->GetSplitMacs(path, &split_macs);
// Treat NULL and empty the same; otherwise we would need to store a hash for
// the entire dictionary (or some other special beacon) to differentiate these
// two cases which are really the same for dictionaries.
if (!initial_split_value || initial_split_value->empty())
return has_hashes ? CLEARED : UNCHANGED;
if (!has_hashes)
return super_mac_valid_ ? TRUSTED_UNKNOWN_VALUE : UNTRUSTED_UNKNOWN_VALUE;
bool has_secure_legacy_id_hashes = false;
std::string keyed_path(path);
keyed_path.push_back('.');
const size_t common_part_length = keyed_path.length();
for (base::DictionaryValue::Iterator it(*initial_split_value); !it.IsAtEnd();
it.Advance()) {
std::map<std::string, std::string>::iterator entry =
split_macs.find(it.key());
if (entry == split_macs.end()) {
invalid_keys->push_back(it.key());
} else {
// Keep the common part from the old |keyed_path| and replace the key to
// get the new |keyed_path|.
keyed_path.replace(common_part_length, std::string::npos, it.key());
switch (outer_->pref_hash_calculator_.Validate(keyed_path, &it.value(),
entry->second)) {
case PrefHashCalculator::VALID:
break;
case SECURE_LEGACY:
// Secure legacy device IDs based hashes are still accepted, but we
// should make sure to notify the caller for them to update the legacy
// hashes.
has_secure_legacy_id_hashes = true;
break;
case PrefHashCalculator::INVALID:
invalid_keys->push_back(it.key());
break;
}
// Remove processed MACs, remaining MACs at the end will also be
// considered invalid.
split_macs.erase(entry);
}
}
// Anything left in the map is missing from the data.
for (std::map<std::string, std::string>::const_iterator it =
split_macs.begin();
it != split_macs.end(); ++it) {
invalid_keys->push_back(it->first);
}
return invalid_keys->empty()
? (has_secure_legacy_id_hashes ? SECURE_LEGACY : UNCHANGED)
: CHANGED;
}
void PrefHashStoreImpl::PrefHashStoreTransactionImpl::StoreSplitHash(
const std::string& path,
const base::DictionaryValue* split_value) {
contents_->RemoveEntry(path);
if (split_value) {
std::unique_ptr<base::DictionaryValue> split_macs =
outer_->ComputeSplitMacs(path, split_value);
for (base::DictionaryValue::Iterator it(*split_macs); !it.IsAtEnd();
it.Advance()) {
const base::StringValue* value_as_string;
bool is_string = it.value().GetAsString(&value_as_string);
DCHECK(is_string);
contents_->SetSplitMac(path, it.key(), value_as_string->GetString());
}
}
super_mac_dirty_ = true;
}
bool PrefHashStoreImpl::PrefHashStoreTransactionImpl::HasHash(
const std::string& path) const {
std::string out_value;
std::map<std::string, std::string> out_values;
return contents_->GetMac(path, &out_value) ||
contents_->GetSplitMacs(path, &out_values);
}
void PrefHashStoreImpl::PrefHashStoreTransactionImpl::ImportHash(
const std::string& path,
const base::Value* hash) {
DCHECK(hash);
contents_->ImportEntry(path, hash);
if (super_mac_valid_)
super_mac_dirty_ = true;
}
void PrefHashStoreImpl::PrefHashStoreTransactionImpl::ClearHash(
const std::string& path) {
if (contents_->RemoveEntry(path) && super_mac_valid_) {
super_mac_dirty_ = true;
}
}
bool PrefHashStoreImpl::PrefHashStoreTransactionImpl::IsSuperMACValid() const {
return super_mac_valid_;
}
bool PrefHashStoreImpl::PrefHashStoreTransactionImpl::StampSuperMac() {
if (!outer_->use_super_mac_ || super_mac_valid_)
return false;
super_mac_dirty_ = true;
return true;
}