blob: 923dded4e611266f7154f4915a0f2925d28db7c4 [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 "services/preferences/tracked/pref_hash_store_impl.h"
#include <stddef.h>
#include <utility>
#include "base/check.h"
#include "base/macros.h"
#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/notreached.h"
#include "services/preferences/tracked/device_id.h"
#include "services/preferences/tracked/hash_store_contents.h"
namespace {
using ValueState =
prefs::mojom::TrackedPreferenceValidationDelegate::ValueState;
// Returns a deterministic ID for this machine.
std::string GenerateDeviceId() {
static base::NoDestructor<std::string> cached_device_id;
if (!cached_device_id->empty())
return *cached_device_id;
std::string device_id;
MachineIdStatus status = GetDeterministicMachineSpecificId(&device_id);
DCHECK(status == MachineIdStatus::NOT_IMPLEMENTED ||
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, 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) {
if (!split_values)
return std::make_unique<base::DictionaryValue>();
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->SetKey(it.key(),
base::Value(ComputeMac(keyed_path, &it.value())));
}
return split_macs;
}
PrefHashStoreImpl::PrefHashStoreTransactionImpl::PrefHashStoreTransactionImpl(
PrefHashStoreImpl* outer,
HashStoreContents* storage)
: outer_(outer),
contents_(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();
}
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 ValueState::TRUSTED_NULL_VALUE;
else if (super_mac_valid_)
return ValueState::TRUSTED_UNKNOWN_VALUE;
else
return ValueState::UNTRUSTED_UNKNOWN_VALUE;
}
PrefHashCalculator::ValidationResult validation_result =
outer_->pref_hash_calculator_.Validate(path, initial_value, last_hash);
switch (validation_result) {
case PrefHashCalculator::VALID:
return ValueState::UNCHANGED;
case PrefHashCalculator::VALID_SECURE_LEGACY:
return ValueState::SECURE_LEGACY;
case PrefHashCalculator::INVALID:
return initial_value ? ValueState::CHANGED : ValueState::CLEARED;
}
NOTREACHED() << "Unexpected PrefHashCalculator::ValidationResult: "
<< validation_result;
return ValueState::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;
}
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 ? ValueState::CLEARED : ValueState::UNCHANGED;
if (!has_hashes)
return super_mac_valid_ ? ValueState::TRUSTED_UNKNOWN_VALUE
: ValueState::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 PrefHashCalculator::VALID_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 ? ValueState::SECURE_LEGACY
: ValueState::UNCHANGED)
: ValueState::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::Value* 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;
}