Commit 03c7a27a authored by Josh Nohle's avatar Josh Nohle Committed by Commit Bot

[DeviceSync v2] Add RemoteDeviceV2Loader class

This class is an analog of RemoveDeviceLoader. It converts the
CryptAuthDevice objects from the CryptAuthDeviceRegistry into
RemoteDevice objects. Some RemoteDevice fields are left empty if the
CryptAuthDevice does not have CryptAuthBetterTogetherDeviceMetadata, for
instance, if the metadata cannot be decrypted. If the public key is
available for a device, a persistent symmetric key (PSK) is derived and
added to the RemoteDevice; otherwise, the PSK is set to an empty string.

An instance of this class can only be used once.

The async calls to SecureMessage are guarded by the default DBus
timeout (currently 25s).

Bug: 951969
Change-Id: I9295b48448c03e5a7df03248949195c60964e0f3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1904330Reviewed-by: default avatarKyle Horimoto <khorimoto@chromium.org>
Commit-Queue: Josh Nohle <nohle@chromium.org>
Cr-Commit-Position: refs/heads/master@{#714040}
parent bd2b03b3
...@@ -45,6 +45,22 @@ cryptauth::BeaconSeed ToCryptAuthSeed(BeaconSeed multidevice_seed) { ...@@ -45,6 +45,22 @@ cryptauth::BeaconSeed ToCryptAuthSeed(BeaconSeed multidevice_seed) {
return cryptauth_seed; return cryptauth_seed;
} }
BeaconSeed FromCryptAuthV2Seed(cryptauthv2::BeaconSeed cryptauth_seed) {
return BeaconSeed(
cryptauth_seed.data(),
base::Time::FromJavaTime(cryptauth_seed.start_time_millis()),
base::Time::FromJavaTime(cryptauth_seed.end_time_millis()));
}
cryptauthv2::BeaconSeed ToCryptAuthV2Seed(BeaconSeed multidevice_seed) {
cryptauthv2::BeaconSeed cryptauth_seed;
cryptauth_seed.set_data(multidevice_seed.data());
cryptauth_seed.set_start_time_millis(
multidevice_seed.start_time().ToJavaTime());
cryptauth_seed.set_end_time_millis(multidevice_seed.end_time().ToJavaTime());
return cryptauth_seed;
}
std::vector<cryptauth::BeaconSeed> ToCryptAuthSeedList( std::vector<cryptauth::BeaconSeed> ToCryptAuthSeedList(
const std::vector<BeaconSeed>& multidevice_seed_list) { const std::vector<BeaconSeed>& multidevice_seed_list) {
std::vector<cryptauth::BeaconSeed> cryptauth_beacon_seeds; std::vector<cryptauth::BeaconSeed> cryptauth_beacon_seeds;
...@@ -67,6 +83,18 @@ std::vector<BeaconSeed> FromCryptAuthSeedList( ...@@ -67,6 +83,18 @@ std::vector<BeaconSeed> FromCryptAuthSeedList(
return multidevice_beacon_seeds; return multidevice_beacon_seeds;
} }
std::vector<BeaconSeed> FromCryptAuthV2SeedRepeatedPtrField(
const google::protobuf::RepeatedPtrField<cryptauthv2::BeaconSeed>&
cryptauth_seed_list) {
std::vector<BeaconSeed> multidevice_beacon_seeds;
std::transform(cryptauth_seed_list.begin(), cryptauth_seed_list.end(),
std::back_inserter(multidevice_beacon_seeds),
[](auto cryptauth_beacon_seed) {
return FromCryptAuthV2Seed(cryptauth_beacon_seed);
});
return multidevice_beacon_seeds;
}
std::ostream& operator<<(std::ostream& stream, const BeaconSeed& beacon_seed) { std::ostream& operator<<(std::ostream& stream, const BeaconSeed& beacon_seed) {
std::string base_64_data; std::string base_64_data;
base::Base64Encode(beacon_seed.data(), &base_64_data); base::Base64Encode(beacon_seed.data(), &base_64_data);
......
...@@ -5,12 +5,14 @@ ...@@ -5,12 +5,14 @@
#ifndef CHROMEOS_COMPONENTS_MULTIDEVICE_BEACON_SEED_H_ #ifndef CHROMEOS_COMPONENTS_MULTIDEVICE_BEACON_SEED_H_
#define CHROMEOS_COMPONENTS_MULTIDEVICE_BEACON_SEED_H_ #define CHROMEOS_COMPONENTS_MULTIDEVICE_BEACON_SEED_H_
#include <google/protobuf/repeated_field.h>
#include <ostream> #include <ostream>
#include <string> #include <string>
#include <vector> #include <vector>
#include "base/time/time.h" #include "base/time/time.h"
#include "chromeos/services/device_sync/proto/cryptauth_api.pb.h" #include "chromeos/services/device_sync/proto/cryptauth_api.pb.h"
#include "chromeos/services/device_sync/proto/cryptauth_better_together_device_metadata.pb.h"
namespace chromeos { namespace chromeos {
...@@ -44,11 +46,19 @@ class BeaconSeed { ...@@ -44,11 +46,19 @@ class BeaconSeed {
BeaconSeed FromCryptAuthSeed(cryptauth::BeaconSeed cryptauth_seed); BeaconSeed FromCryptAuthSeed(cryptauth::BeaconSeed cryptauth_seed);
cryptauth::BeaconSeed ToCryptAuthSeed(BeaconSeed multidevice_seed); cryptauth::BeaconSeed ToCryptAuthSeed(BeaconSeed multidevice_seed);
BeaconSeed FromCryptAuthV2Seed(cryptauthv2::BeaconSeed cryptauth_seed);
cryptauthv2::BeaconSeed ToCryptAuthV2Seed(BeaconSeed multidevice_seed);
std::vector<cryptauth::BeaconSeed> ToCryptAuthSeedList( std::vector<cryptauth::BeaconSeed> ToCryptAuthSeedList(
const std::vector<BeaconSeed>& cryptauth_seed_list); const std::vector<BeaconSeed>& cryptauth_seed_list);
std::vector<BeaconSeed> FromCryptAuthSeedList( std::vector<BeaconSeed> FromCryptAuthSeedList(
const std::vector<cryptauth::BeaconSeed>& cryptauth_seed_list); const std::vector<cryptauth::BeaconSeed>& cryptauth_seed_list);
std::vector<BeaconSeed> FromCryptAuthV2SeedRepeatedPtrField(
const google::protobuf::RepeatedPtrField<cryptauthv2::BeaconSeed>&
cryptauth_seed_list);
std::ostream& operator<<(std::ostream& stream, const BeaconSeed& beacon_seed); std::ostream& operator<<(std::ostream& stream, const BeaconSeed& beacon_seed);
} // namespace multidevice } // namespace multidevice
......
...@@ -127,6 +127,8 @@ static_library("device_sync") { ...@@ -127,6 +127,8 @@ static_library("device_sync") {
"remote_device_provider.h", "remote_device_provider.h",
"remote_device_provider_impl.cc", "remote_device_provider_impl.cc",
"remote_device_provider_impl.h", "remote_device_provider_impl.h",
"remote_device_v2_loader.cc",
"remote_device_v2_loader.h",
"software_feature_manager.h", "software_feature_manager.h",
"software_feature_manager_impl.cc", "software_feature_manager_impl.cc",
"software_feature_manager_impl.h", "software_feature_manager_impl.h",
...@@ -272,6 +274,7 @@ source_set("unit_tests") { ...@@ -272,6 +274,7 @@ source_set("unit_tests") {
"device_sync_service_unittest.cc", "device_sync_service_unittest.cc",
"remote_device_loader_unittest.cc", "remote_device_loader_unittest.cc",
"remote_device_provider_impl_unittest.cc", "remote_device_provider_impl_unittest.cc",
"remote_device_v2_loader_unittest.cc",
"software_feature_manager_impl_unittest.cc", "software_feature_manager_impl_unittest.cc",
"sync_scheduler_impl_unittest.cc", "sync_scheduler_impl_unittest.cc",
] ]
......
// Copyright 2019 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 "chromeos/services/device_sync/remote_device_v2_loader.h"
#include <utility>
#include "base/bind.h"
#include "base/memory/ptr_util.h"
#include "base/no_destructor.h"
#include "chromeos/components/multidevice/beacon_seed.h"
#include "chromeos/components/multidevice/logging/logging.h"
#include "chromeos/components/multidevice/secure_message_delegate_impl.h"
#include "chromeos/services/device_sync/async_execution_time_metrics_logger.h"
#include "chromeos/services/device_sync/cryptauth_task_metrics_logger.h"
namespace chromeos {
namespace device_sync {
// static
RemoteDeviceV2Loader::Factory* RemoteDeviceV2Loader::Factory::test_factory_ =
nullptr;
// static
RemoteDeviceV2Loader::Factory* RemoteDeviceV2Loader::Factory::Get() {
if (test_factory_)
return test_factory_;
static base::NoDestructor<RemoteDeviceV2Loader::Factory> factory;
return factory.get();
}
// static
void RemoteDeviceV2Loader::Factory::SetFactoryForTesting(
Factory* test_factory) {
test_factory_ = test_factory;
}
RemoteDeviceV2Loader::Factory::~Factory() = default;
std::unique_ptr<RemoteDeviceV2Loader>
RemoteDeviceV2Loader::Factory::BuildInstance() {
return base::WrapUnique(new RemoteDeviceV2Loader());
}
RemoteDeviceV2Loader::RemoteDeviceV2Loader()
: secure_message_delegate_(
multidevice::SecureMessageDelegateImpl::Factory::NewInstance()) {}
RemoteDeviceV2Loader::~RemoteDeviceV2Loader() = default;
void RemoteDeviceV2Loader::Load(
const CryptAuthDeviceRegistry::InstanceIdToDeviceMap& id_to_device_map,
const std::string& user_id,
const std::string& user_private_key,
LoadCallback callback) {
DCHECK(!user_id.empty());
DCHECK(!user_private_key.empty());
DCHECK(callback_.is_null());
callback_ = std::move(callback);
DCHECK(id_to_device_map_.empty());
id_to_device_map_ = id_to_device_map;
if (id_to_device_map_.empty()) {
std::move(callback_).Run(remote_devices_);
return;
}
DCHECK(remaining_ids_to_process_.empty());
for (const auto& id_device_pair : id_to_device_map_)
remaining_ids_to_process_.insert(id_device_pair.first);
for (const auto& id_device_pair : id_to_device_map_) {
if (!id_device_pair.second.better_together_device_metadata ||
id_device_pair.second.better_together_device_metadata->public_key()
.empty()) {
AddRemoteDevice(id_device_pair.second, user_id, std::string() /* psk */);
continue;
}
// Performs ECDH key agreement to generate a shared secret between the local
// device and the remote device of |id_device_pair|.
secure_message_delegate_->DeriveKey(
user_private_key,
id_device_pair.second.better_together_device_metadata->public_key(),
base::Bind(&RemoteDeviceV2Loader::OnPskDerived, base::Unretained(this),
id_device_pair.second, user_id));
}
}
void RemoteDeviceV2Loader::OnPskDerived(const CryptAuthDevice& device,
const std::string& user_id,
const std::string& psk) {
if (psk.empty())
PA_LOG(WARNING) << "Derived persistent symmetric key is empty.";
AddRemoteDevice(device, user_id, psk);
}
void RemoteDeviceV2Loader::AddRemoteDevice(const CryptAuthDevice& device,
const std::string& user_id,
const std::string& psk) {
const base::Optional<cryptauthv2::BetterTogetherDeviceMetadata>&
beto_metadata = device.better_together_device_metadata;
remote_devices_.emplace_back(
user_id, device.instance_id(), device.device_name,
beto_metadata ? beto_metadata->no_pii_device_name() : std::string(),
beto_metadata ? beto_metadata->public_key() : std::string(), psk,
device.last_update_time.ToJavaTime(), device.feature_states,
beto_metadata ? multidevice::FromCryptAuthV2SeedRepeatedPtrField(
beto_metadata->beacon_seeds())
: std::vector<multidevice::BeaconSeed>());
remaining_ids_to_process_.erase(device.instance_id());
if (remaining_ids_to_process_.empty())
std::move(callback_).Run(remote_devices_);
}
} // namespace device_sync
} // namespace chromeos
// Copyright 2019 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.
#ifndef CHROMEOS_SERVICES_DEVICE_SYNC_REMOTE_DEVICE_V2_LOADER_H_
#define CHROMEOS_SERVICES_DEVICE_SYNC_REMOTE_DEVICE_V2_LOADER_H_
#include <memory>
#include <string>
#include "base/callback.h"
#include "base/containers/flat_set.h"
#include "chromeos/components/multidevice/remote_device.h"
#include "chromeos/services/device_sync/cryptauth_device.h"
#include "chromeos/services/device_sync/cryptauth_device_registry.h"
namespace chromeos {
namespace multidevice {
class SecureMessageDelegate;
} // namespace multidevice
namespace device_sync {
// Converts the given CryptAuthDevices into RemoteDevices. Some RemoteDevice
// fields are left empty if the CryptAuthDevice does not have
// CryptAuthBetterTogetherDeviceMetadata, for instance, if the metadata cannot
// be decrypted. If the public key is available for a device, a persistent
// symmetric key (PSK) is derived and added to the RemoteDevice; otherwise, the
// PSK is set to an empty string.
//
// A RemoteDeviceV2Loader object is designed to be used for only one Load()
// call. For a new attempt, a new object should be created. Note: The async
// calls to SecureMessage are guarded by the default DBus timeout (currently
// 25s).
class RemoteDeviceV2Loader {
public:
using LoadCallback =
base::OnceCallback<void(const multidevice::RemoteDeviceList&)>;
class Factory {
public:
static Factory* Get();
static void SetFactoryForTesting(Factory* test_factory);
virtual ~Factory();
virtual std::unique_ptr<RemoteDeviceV2Loader> BuildInstance();
private:
static Factory* test_factory_;
};
virtual ~RemoteDeviceV2Loader();
// Converts the input CryptAuthDevices to RemoteDevices. All devices are
// converted but some remote device information might be missing, for
// instance, if the CryptAuthBetterTogetherMetadata is not available.
// |id_to_device_map|: A map from Instance ID to CryptAuthDevice which will be
// converted to a list of RemoteDevices.
// |user_id|: The account ID of the user who owns the devices.
// |user_private_key|: The private key of the user's local device. Used to
// derive the persistent symmetric key (PSK).
// |callback|: Invoked when the conversion is complete.
virtual void Load(
const CryptAuthDeviceRegistry::InstanceIdToDeviceMap& id_to_device_map,
const std::string& user_id,
const std::string& user_private_key,
LoadCallback callback);
private:
RemoteDeviceV2Loader();
// Disallow copy and assign.
RemoteDeviceV2Loader(const RemoteDeviceV2Loader&) = delete;
RemoteDeviceV2Loader& operator=(const RemoteDeviceV2Loader&) = delete;
void OnPskDerived(const CryptAuthDevice& device,
const std::string& user_id,
const std::string& psk);
void AddRemoteDevice(const CryptAuthDevice& device,
const std::string& user_id,
const std::string& psk);
LoadCallback callback_;
CryptAuthDeviceRegistry::InstanceIdToDeviceMap id_to_device_map_;
base::flat_set<std::string> remaining_ids_to_process_;
multidevice::RemoteDeviceList remote_devices_;
std::unique_ptr<multidevice::SecureMessageDelegate> secure_message_delegate_;
};
} // namespace device_sync
} // namespace chromeos
#endif // CHROMEOS_SERVICES_DEVICE_SYNC_REMOTE_DEVICE_V2_LOADER_H_
// Copyright 2019 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 <algorithm>
#include <memory>
#include <string>
#include <utility>
#include "base/bind.h"
#include "chromeos/components/multidevice/fake_secure_message_delegate.h"
#include "chromeos/components/multidevice/remote_device.h"
#include "chromeos/services/device_sync/cryptauth_device.h"
#include "chromeos/services/device_sync/proto/cryptauth_devicesync.pb.h"
#include "chromeos/services/device_sync/proto/cryptauth_v2_test_util.h"
#include "chromeos/services/device_sync/remote_device_v2_loader.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace device_sync {
namespace {
// Prefixes for RemoteDevice fields.
const char kDeviceNamePrefix[] = "device_name";
const char kNoPiiDeviceNamePrefix[] = "no_pii_here";
const char kPublicKeyPrefix[] = "public_key";
const char kInstanceIdPrefix[] = "instance_id";
const char kPskPlaceholder[] = "psk_placeholder";
// The id of the user who the remote devices belong to.
const char kUserId[] = "example@gmail.com";
// The public key of the user's local device.
const char kUserPublicKey[] = "user_public_key";
// BeaconSeed values.
const int64_t kBeaconSeedStartTimeMs = 1000;
const int64_t kBeaconSeedEndTimeMs = 2000;
const char kBeaconSeedData[] = "beacon_seed_data";
// The "DeviceSync:BetterTogether" public key prefix.
const char kDeviceSyncBetterTogetherPublicKeyPrefix[] = "ds_beto_pub_key";
// Last update time in milliseconds
const int64_t kLastUpdateTimeMs = 3000;
// Creates a CryptAuthDevice with |suffix| appended to each predetermined string
// field. If |has_beto_metadata| is false,
// CryptAuthDevice.better_together_device_metadata is not set, and if
// |has_public_key| is false,
// CryptAuthDevice.better_together_device_metadata.public_key is not set.
CryptAuthDevice CreateCryptAuthDevice(const std::string& suffix,
bool has_beto_metadata,
bool has_public_key) {
base::Optional<cryptauthv2::BetterTogetherDeviceMetadata> beto_metadata;
if (has_beto_metadata) {
beto_metadata = cryptauthv2::BetterTogetherDeviceMetadata();
beto_metadata->set_no_pii_device_name(kNoPiiDeviceNamePrefix + suffix);
cryptauthv2::BeaconSeed* beacon_seed = beto_metadata->add_beacon_seeds();
beacon_seed->set_start_time_millis(kBeaconSeedStartTimeMs);
beacon_seed->set_end_time_millis(kBeaconSeedEndTimeMs);
beacon_seed->set_data(kBeaconSeedData + suffix);
if (has_public_key)
beto_metadata->set_public_key(kPublicKeyPrefix + suffix);
}
return CryptAuthDevice(kInstanceIdPrefix + suffix, kDeviceNamePrefix + suffix,
kDeviceSyncBetterTogetherPublicKeyPrefix + suffix,
base::Time::FromJavaTime(kLastUpdateTimeMs),
beto_metadata,
{{multidevice::SoftwareFeature::kBetterTogetherHost,
multidevice::SoftwareFeatureState::kSupported}});
}
// Creates a RemoteDevice with |suffix| appended to each predetermined string
// field. If |has_beto_metadata| is false, RemoteDevice.pii_free_name is not
// set. If |has_public_key| is false, RemoteDevice.public_key and
// RemoteDevice.persistent_symmetric_key is not set. If |has_beacon_seeds| is
// false, RemoteDevice.beacon_seeds is not set.
multidevice::RemoteDevice CreateRemoteDevice(const std::string& suffix,
bool has_pii_free_name,
bool has_public_key,
bool has_beacon_seeds) {
std::string pii_free_name =
has_pii_free_name ? kNoPiiDeviceNamePrefix + suffix : std::string();
std::string public_key =
has_public_key ? kPublicKeyPrefix + suffix : std::string();
std::string psk = has_public_key ? kPskPlaceholder : std::string();
std::vector<multidevice::BeaconSeed> beacon_seeds;
if (has_beacon_seeds) {
beacon_seeds.emplace_back(kBeaconSeedData + suffix,
base::Time::FromJavaTime(kBeaconSeedStartTimeMs),
base::Time::FromJavaTime(kBeaconSeedEndTimeMs));
}
return multidevice::RemoteDevice(
kUserId, kInstanceIdPrefix + suffix, kDeviceNamePrefix + suffix,
pii_free_name, public_key, psk, kLastUpdateTimeMs,
{{multidevice::SoftwareFeature::kBetterTogetherHost,
multidevice::SoftwareFeatureState::kSupported}},
beacon_seeds);
}
} // namespace
class DeviceSyncRemoteDeviceV2LoaderTest : public testing::Test {
public:
DeviceSyncRemoteDeviceV2LoaderTest()
: fake_secure_message_delegate_factory_(
std::make_unique<multidevice::FakeSecureMessageDelegateFactory>()),
user_private_key_(fake_secure_message_delegate_factory_->instance()
->GetPrivateKeyForPublicKey(kUserPublicKey)) {}
~DeviceSyncRemoteDeviceV2LoaderTest() override = default;
// testing::Test:
void SetUp() override {
multidevice::SecureMessageDelegateImpl::Factory::SetInstanceForTesting(
fake_secure_message_delegate_factory_.get());
}
// testing::Test:
void TearDown() override {
multidevice::SecureMessageDelegateImpl::Factory::SetInstanceForTesting(
nullptr);
}
void CallLoad(std::vector<CryptAuthDevice> device_list) {
CryptAuthDeviceRegistry::InstanceIdToDeviceMap id_to_device_map;
for (const auto& device : device_list)
id_to_device_map.insert_or_assign(device.instance_id(), device);
loader_ = RemoteDeviceV2Loader::Factory::Get()->BuildInstance();
loader_->Load(
id_to_device_map, kUserId, user_private_key_,
base::BindOnce(&DeviceSyncRemoteDeviceV2LoaderTest::OnLoadFinished,
base::Unretained(this)));
}
void OnLoadFinished(const multidevice::RemoteDeviceList& remote_devices) {
remote_devices_ = remote_devices;
}
void VerifyLoad(
const multidevice::RemoteDeviceList& expected_remote_devices) {
ASSERT_TRUE(remote_devices_);
EXPECT_EQ(expected_remote_devices.size(), remote_devices_->size());
for (const auto& expected_device : expected_remote_devices) {
std::string expected_instance_id = expected_device.instance_id;
auto it = std::find_if(
remote_devices_->begin(), remote_devices_->end(),
[&expected_instance_id](const multidevice::RemoteDevice& device) {
return device.instance_id == expected_instance_id;
});
ASSERT_FALSE(it == remote_devices_->end());
multidevice::RemoteDevice remote_device = *it;
// Because of the way FakeSecureMessageDelegate is implemented, we do not
// know the form of the derived persistent symmetric key, only if it is
// empty or not. After checking if both the actual and expected keys are
// empty or not empty, replace the placeholder PSK with the PSK of the
// expected RemoteDevice. This allows us to directly compare two
// RemoteDevice objects.
EXPECT_EQ(expected_device.persistent_symmetric_key.empty(),
remote_device.persistent_symmetric_key.empty());
remote_device.persistent_symmetric_key =
expected_device.persistent_symmetric_key;
EXPECT_EQ(expected_device, remote_device);
}
}
protected:
// Null until Load() finishes.
base::Optional<multidevice::RemoteDeviceList> remote_devices_;
std::unique_ptr<multidevice::FakeSecureMessageDelegateFactory>
fake_secure_message_delegate_factory_;
std::string user_private_key_;
std::unique_ptr<RemoteDeviceV2Loader> loader_;
};
TEST_F(DeviceSyncRemoteDeviceV2LoaderTest, NoDevices) {
CallLoad({} /* device_list */);
VerifyLoad({} /* expected_remote_devices */);
}
TEST_F(DeviceSyncRemoteDeviceV2LoaderTest, Success) {
CallLoad({CreateCryptAuthDevice("device1", true /* has_beto_metadata */,
true /* has_public_key */),
CreateCryptAuthDevice("device2", true /* has_beto_metadata */,
false /* has_public_key */),
CreateCryptAuthDevice("device3", false /* has_beto_metadata */,
false /* has_public_key */)});
VerifyLoad({CreateRemoteDevice("device1", true /* has_pii_free_name */,
true /* has_public_key */,
true /* has_beacon_seeds */),
CreateRemoteDevice("device2", true /* has_pii_free_name */,
false /* has_public_key */,
true /* has_beacon_seeds */),
CreateRemoteDevice("device3", false /* has_pii_free_name */,
false /* has_public_key */,
false /* has_beacon_seeds */)});
}
} // namespace device_sync
} // namespace chromeos
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment