| // 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 |