blob: 212a33071255b8e9a9d122f581fca7dfb0f99ba2 [file] [log] [blame]
// Copyright 2014 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 "chrome/browser/chromeos/login/easy_unlock/easy_unlock_key_manager.h"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "chrome/browser/chromeos/login/easy_unlock/easy_unlock_key_names.h"
#include "chrome/browser/chromeos/login/easy_unlock/easy_unlock_tpm_key_manager.h"
#include "chrome/browser/chromeos/login/easy_unlock/easy_unlock_tpm_key_manager_factory.h"
#include "chromeos/components/multidevice/logging/logging.h"
#include "components/account_id/account_id.h"
namespace chromeos {
EasyUnlockKeyManager::EasyUnlockKeyManager() : weak_ptr_factory_(this) {}
EasyUnlockKeyManager::~EasyUnlockKeyManager() {}
void EasyUnlockKeyManager::RefreshKeys(const UserContext& user_context,
const base::ListValue& remote_devices,
const RefreshKeysCallback& callback) {
base::Closure do_refresh_keys =
base::Bind(&EasyUnlockKeyManager::RefreshKeysWithTpmKeyPresent,
weak_ptr_factory_.GetWeakPtr(), user_context,
base::Owned(remote_devices.DeepCopy()), callback);
EasyUnlockTpmKeyManager* tpm_key_manager =
EasyUnlockTpmKeyManagerFactory::GetInstance()->GetForUser(
user_context.GetAccountId().GetUserEmail());
if (!tpm_key_manager) {
PA_LOG(ERROR) << "No TPM key manager.";
callback.Run(false);
return;
}
// Private TPM key is needed only when adding new keys.
if (remote_devices.empty() ||
tpm_key_manager->PrepareTpmKey(false /* check_private_key */,
do_refresh_keys)) {
do_refresh_keys.Run();
} else {
// In case Chrome is supposed to restart to apply user session flags, the
// Chrome restart will be postponed until Easy Sign-in keys are refreshed.
// This is to ensure that creating TPM key does not hang if TPM system
// loading takes too much time. Note that in normal circumstances the
// chances that TPM slot cannot be loaded should be extremely low.
// TODO(tbarzic): Add some metrics to measure if the timeout even gets hit.
tpm_key_manager->StartGetSystemSlotTimeoutMs(2000);
}
}
void EasyUnlockKeyManager::RefreshKeysWithTpmKeyPresent(
const UserContext& user_context,
base::ListValue* remote_devices,
const RefreshKeysCallback& callback) {
EasyUnlockTpmKeyManager* tpm_key_manager =
EasyUnlockTpmKeyManagerFactory::GetInstance()->GetForUser(
user_context.GetAccountId().GetUserEmail());
const std::string tpm_public_key =
tpm_key_manager->GetPublicTpmKey(user_context.GetAccountId());
EasyUnlockDeviceKeyDataList devices;
if (!RemoteDeviceRefListToDeviceDataList(*remote_devices, &devices))
devices.clear();
write_operation_queue_.push_back(
std::make_unique<EasyUnlockRefreshKeysOperation>(
user_context, tpm_public_key, devices,
base::Bind(&EasyUnlockKeyManager::OnKeysRefreshed,
weak_ptr_factory_.GetWeakPtr(), callback)));
RunNextOperation();
}
void EasyUnlockKeyManager::GetDeviceDataList(
const UserContext& user_context,
const GetDeviceDataListCallback& callback) {
read_operation_queue_.push_back(std::make_unique<EasyUnlockGetKeysOperation>(
user_context, base::Bind(&EasyUnlockKeyManager::OnKeysFetched,
weak_ptr_factory_.GetWeakPtr(), callback)));
RunNextOperation();
}
// static
void EasyUnlockKeyManager::DeviceDataToRemoteDeviceDictionary(
const AccountId& account_id,
const EasyUnlockDeviceKeyData& data,
base::DictionaryValue* dict) {
dict->SetString(key_names::kKeyBluetoothAddress, data.bluetooth_address);
dict->SetInteger(key_names::kKeyBluetoothType,
static_cast<int>(data.bluetooth_type));
dict->SetString(key_names::kKeyPsk, data.psk);
std::unique_ptr<base::DictionaryValue> permit_record(
new base::DictionaryValue);
dict->Set(key_names::kKeyPermitRecord, std::move(permit_record));
dict->SetString(key_names::kKeyPermitId, data.public_key);
dict->SetString(key_names::kKeyPermitData, data.public_key);
dict->SetString(key_names::kKeyPermitType, key_names::kPermitTypeLicence);
dict->SetString(key_names::kKeyPermitPermitId,
base::StringPrintf(key_names::kPermitPermitIdFormat,
account_id.GetUserEmail().c_str()));
dict->SetString(key_names::kKeySerializedBeaconSeeds,
data.serialized_beacon_seeds);
dict->SetBoolean(key_names::kKeyUnlockKey, data.unlock_key);
}
// static
bool EasyUnlockKeyManager::RemoteDeviceDictionaryToDeviceData(
const base::DictionaryValue& dict,
EasyUnlockDeviceKeyData* data) {
std::string bluetooth_address;
std::string public_key;
std::string psk;
if (!dict.GetString(key_names::kKeyBluetoothAddress, &bluetooth_address) ||
!dict.GetString(key_names::kKeyPermitId, &public_key) ||
!dict.GetString(key_names::kKeyPsk, &psk)) {
return false;
}
// TODO(tengs): Move this conditional up once we can be certain that the
// dictionary will contain the Bluetooth type key.
int bluetooth_type_as_int;
if (dict.GetInteger(key_names::kKeyBluetoothType, &bluetooth_type_as_int)) {
if (bluetooth_type_as_int >= EasyUnlockDeviceKeyData::NUM_BLUETOOTH_TYPES) {
PA_LOG(ERROR) << "Invalid Bluetooth type: " << bluetooth_type_as_int;
} else {
data->bluetooth_type =
static_cast<EasyUnlockDeviceKeyData::BluetoothType>(
bluetooth_type_as_int);
}
}
std::string serialized_beacon_seeds;
if (dict.GetString(key_names::kKeySerializedBeaconSeeds,
&serialized_beacon_seeds)) {
data->serialized_beacon_seeds = serialized_beacon_seeds;
} else {
PA_LOG(ERROR) << "Failed to parse key data: "
<< "expected serialized_beacon_seeds.";
}
bool unlock_key;
if (!dict.GetBoolean(key_names::kKeyUnlockKey, &unlock_key)) {
// If GetBoolean() fails, that means we're reading a Dictionary from
// user prefs which did not include the bool when it was stored. That means
// it's an older Dictionary that didn't include this |unlock_key| field --
// only one device was persisted, and it was implicitly assumed to be the
// unlock key -- thus |unlock_key| should default to being true.
unlock_key = true;
}
data->unlock_key = unlock_key;
data->bluetooth_address.swap(bluetooth_address);
data->public_key.swap(public_key);
data->psk.swap(psk);
return true;
}
// static
void EasyUnlockKeyManager::DeviceDataListToRemoteDeviceList(
const AccountId& account_id,
const EasyUnlockDeviceKeyDataList& data_list,
base::ListValue* device_list) {
device_list->Clear();
for (size_t i = 0; i < data_list.size(); ++i) {
std::unique_ptr<base::DictionaryValue> device_dict(
new base::DictionaryValue);
DeviceDataToRemoteDeviceDictionary(account_id, data_list[i],
device_dict.get());
device_list->Append(std::move(device_dict));
}
}
// static
bool EasyUnlockKeyManager::RemoteDeviceRefListToDeviceDataList(
const base::ListValue& device_list,
EasyUnlockDeviceKeyDataList* data_list) {
EasyUnlockDeviceKeyDataList parsed_devices;
for (base::ListValue::const_iterator it = device_list.begin();
it != device_list.end(); ++it) {
const base::DictionaryValue* dict;
if (!it->GetAsDictionary(&dict) || !dict)
return false;
EasyUnlockDeviceKeyData data;
if (!RemoteDeviceDictionaryToDeviceData(*dict, &data))
return false;
parsed_devices.push_back(data);
}
data_list->swap(parsed_devices);
return true;
}
// static
std::string EasyUnlockKeyManager::GetKeyLabel(size_t key_index) {
return base::StringPrintf("%s%zu", key_names::kKeyLabelPrefix, key_index);
}
void EasyUnlockKeyManager::RunNextOperation() {
if (pending_write_operation_ || pending_read_operation_)
return;
if (!write_operation_queue_.empty()) {
pending_write_operation_ = std::move(write_operation_queue_.front());
write_operation_queue_.pop_front();
pending_write_operation_->Start();
} else if (!read_operation_queue_.empty()) {
pending_read_operation_ = std::move(read_operation_queue_.front());
read_operation_queue_.pop_front();
pending_read_operation_->Start();
}
}
void EasyUnlockKeyManager::OnKeysRefreshed(const RefreshKeysCallback& callback,
bool refresh_success) {
if (!callback.is_null())
callback.Run(refresh_success);
DCHECK(pending_write_operation_);
pending_write_operation_.reset();
RunNextOperation();
}
void EasyUnlockKeyManager::OnKeysFetched(
const GetDeviceDataListCallback& callback,
bool fetch_success,
const EasyUnlockDeviceKeyDataList& fetched_data) {
if (!callback.is_null())
callback.Run(fetch_success, fetched_data);
DCHECK(pending_read_operation_);
pending_read_operation_.reset();
RunNextOperation();
}
} // namespace chromeos