blob: ec1046bb41c8d91e10d9e6b4fbb5157c6a044167 [file] [log] [blame]
// Copyright 2021 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 <memory>
#include <vector>
#include "base/strings/string_piece.h"
#include "base/test/scoped_feature_list.h"
#include "base/time/time.h"
#include "chrome/browser/webauthn/cablev2_devices.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/test/browser_task_environment.h"
#include "device/fido/cable/cable_discovery_data.h"
#include "device/fido/features.h"
#include "testing/gtest/include/gtest/gtest.h"
using cablev2::KnownDevices;
using cablev2::MergeDevices;
using device::cablev2::Pairing;
namespace {
enum class Channel {
UNKNOWN = 0,
STABLE = 1,
BETA = 2,
DEV = 3,
CANARY = 4,
DEVELOPER = 5,
};
constexpr uint8_t kPubKey1 = 1;
constexpr uint8_t kPubKey2 = 2;
constexpr uint8_t kPubKey3 = 3;
constexpr uint8_t kPubKey4 = 4;
constexpr uint8_t kPubKey5 = 5;
constexpr uint8_t kPubKey6 = 6;
constexpr uint8_t kPubKey7 = 7;
constexpr uint8_t kPubKey8 = 8;
const base::Time kTime9 = base::Time::FromTimeT(9);
const base::Time kTime10 = base::Time::FromTimeT(10);
const base::Time kTime11 = base::Time::FromTimeT(11);
std::unique_ptr<Pairing> SyncedDevice(const char* name,
uint8_t public_key_id,
Channel channel,
base::Time update) {
auto ret = std::make_unique<Pairing>();
ret->name = name;
ret->from_sync_deviceinfo = true;
ret->last_updated = update;
ret->channel_priority = static_cast<int>(channel);
ret->peer_public_key_x962 = {0};
ret->peer_public_key_x962[0] = public_key_id;
return ret;
}
std::unique_ptr<Pairing> LinkedDevice(const char* name, uint8_t public_key_id) {
auto ret = std::make_unique<Pairing>();
ret->name = name;
ret->peer_public_key_x962 = {0};
ret->peer_public_key_x962[0] = public_key_id;
return ret;
}
TEST(CableV2DeviceMerging, SyncOverridesForSameDevice) {
// If there's a record from the same device (i.e. same public key) obtained
// via syncing and linking, syncing takes priority.
auto known_devices = std::make_unique<KnownDevices>();
known_devices->synced_devices.emplace_back(
SyncedDevice("S1", kPubKey1, Channel::STABLE, kTime10));
known_devices->linked_devices.emplace_back(LinkedDevice("L1", kPubKey1));
std::vector<std::unique_ptr<Pairing>> result =
MergeDevices(std::move(known_devices), &icu::Locale::getUS());
ASSERT_EQ(result.size(), 1u);
EXPECT_EQ(result[0]->name, "S1");
}
TEST(CableV2DeviceMerging, MostRecentSyncFirst) {
// If there are multiple pairings for the same device (i.e. same public key)
// then the most recent sync takes priority.
auto known_devices = std::make_unique<KnownDevices>();
std::vector<std::unique_ptr<Pairing>>& synced = known_devices->synced_devices;
synced.emplace_back(SyncedDevice("S1", kPubKey1, Channel::STABLE, kTime10));
synced.emplace_back(SyncedDevice("S1", kPubKey1, Channel::STABLE, kTime9));
synced.emplace_back(SyncedDevice("S1", kPubKey1, Channel::STABLE, kTime11));
std::vector<std::unique_ptr<Pairing>> result =
MergeDevices(std::move(known_devices), &icu::Locale::getUS());
ASSERT_EQ(result.size(), 1u);
EXPECT_EQ(result[0]->name, "S1");
EXPECT_EQ(result[0]->last_updated, kTime11);
}
TEST(CableV2DeviceMerging, SyncOrderedByChannel) {
// Multiple Sync records from different installations on the same device are
// ordered by unstable channel first and then by update time. Pairings with
// the same name should be grouped together.
auto known_devices = std::make_unique<KnownDevices>();
std::vector<std::unique_ptr<Pairing>>& synced = known_devices->synced_devices;
synced.emplace_back(SyncedDevice("S1", kPubKey1, Channel::STABLE, kTime10));
synced.emplace_back(SyncedDevice("S1", kPubKey2, Channel::BETA, kTime10));
synced.emplace_back(SyncedDevice("S1", kPubKey3, Channel::DEV, kTime9));
synced.emplace_back(SyncedDevice("S1", kPubKey4, Channel::DEV, kTime10));
synced.emplace_back(SyncedDevice("S2", kPubKey5, Channel::STABLE, kTime10));
synced.emplace_back(SyncedDevice("S1", kPubKey6, Channel::DEV, kTime11));
synced.emplace_back(SyncedDevice("S1", kPubKey7, Channel::CANARY, kTime10));
synced.emplace_back(
SyncedDevice("S1", kPubKey8, Channel::DEVELOPER, kTime10));
std::vector<std::unique_ptr<Pairing>> result =
MergeDevices(std::move(known_devices), &icu::Locale::getUS());
std::vector<std::unique_ptr<Pairing>> expected;
expected.emplace_back(
SyncedDevice("S1", kPubKey1, Channel::DEVELOPER, kTime10));
expected.emplace_back(SyncedDevice("S1", kPubKey1, Channel::CANARY, kTime10));
expected.emplace_back(SyncedDevice("S1", kPubKey1, Channel::DEV, kTime11));
expected.emplace_back(SyncedDevice("S1", kPubKey1, Channel::DEV, kTime10));
expected.emplace_back(SyncedDevice("S1", kPubKey1, Channel::DEV, kTime9));
expected.emplace_back(SyncedDevice("S1", kPubKey1, Channel::BETA, kTime10));
expected.emplace_back(SyncedDevice("S1", kPubKey1, Channel::STABLE, kTime10));
expected.emplace_back(SyncedDevice("S2", kPubKey2, Channel::STABLE, kTime10));
ASSERT_EQ(result.size(), expected.size());
for (size_t i = 0; i < result.size(); i++) {
SCOPED_TRACE(i);
EXPECT_EQ(result[i]->name, expected[i]->name);
EXPECT_EQ(result[i]->channel_priority, expected[i]->channel_priority);
EXPECT_EQ(result[i]->last_updated, expected[i]->last_updated);
}
}
TEST(CableV2DeviceMerging, Alphabetical) {
// Names of different devices should be sorted alphabetically.
auto known_devices = std::make_unique<KnownDevices>();
std::vector<std::unique_ptr<Pairing>>& s = known_devices->synced_devices;
s.emplace_back(SyncedDevice("Charlie", kPubKey1, Channel::STABLE, kTime10));
s.emplace_back(SyncedDevice("Alice", kPubKey2, Channel::STABLE, kTime10));
s.emplace_back(SyncedDevice("Bob", kPubKey3, Channel::STABLE, kTime10));
std::vector<std::unique_ptr<Pairing>>& l = known_devices->linked_devices;
l.emplace_back(LinkedDevice("Denise", kPubKey4));
l.emplace_back(LinkedDevice("alicia", kPubKey5));
std::vector<std::unique_ptr<Pairing>> result =
MergeDevices(std::move(known_devices), &icu::Locale::getUS());
std::vector<std::string> expected = {
"Alice", "alicia", "Bob", "Charlie", "Denise",
};
ASSERT_EQ(result.size(), expected.size());
for (size_t i = 0; i < result.size(); i++) {
SCOPED_TRACE(i);
EXPECT_EQ(result[i]->name, expected[i]);
}
}
class CableV2DevicesProfileTest : public testing::Test {
// The `ScopedFeatureList` must be created before initializing any threads.
base::test::ScopedFeatureList scoped_feature_list_{
device::kWebAuthPhoneSupport};
// A `BrowserTaskEnvironment` needs to be in-scope in order to create a
// `TestingProfile`.
content::BrowserTaskEnvironment task_environment_;
};
TEST_F(CableV2DevicesProfileTest, InitiallyEmpty) {
TestingProfile profile;
std::unique_ptr<KnownDevices> known_devices =
KnownDevices::FromProfile(&profile);
EXPECT_EQ(known_devices->synced_devices.size(), 0u);
EXPECT_EQ(known_devices->linked_devices.size(), 0u);
}
std::unique_ptr<Pairing> PairingWithAllFields() {
auto ret = std::make_unique<Pairing>();
ret->contact_id = {1, 2, 3, 4, 5};
ret->id = {6, 7, 8, 9, 10};
ret->secret = {11, 12, 13, 14, 15};
ret->peer_public_key_x962 = {16, 17, 18, 19, 20};
ret->name = "Pairing";
return ret;
}
TEST_F(CableV2DevicesProfileTest, StoreAndFetch) {
TestingProfile profile;
cablev2::AddPairing(profile.GetPrefs(), PairingWithAllFields(), {});
std::unique_ptr<KnownDevices> known_devices =
KnownDevices::FromProfile(&profile);
EXPECT_EQ(known_devices->synced_devices.size(), 0u);
ASSERT_EQ(known_devices->linked_devices.size(), 1u);
std::unique_ptr<Pairing> expected = PairingWithAllFields();
const Pairing* const found = known_devices->linked_devices[0].get();
EXPECT_EQ(found->contact_id, expected->contact_id);
EXPECT_EQ(found->id, expected->id);
EXPECT_EQ(found->secret, expected->secret);
EXPECT_EQ(found->peer_public_key_x962, expected->peer_public_key_x962);
EXPECT_EQ(found->name, expected->name);
EXPECT_EQ(found->from_sync_deviceinfo, expected->from_sync_deviceinfo);
}
TEST_F(CableV2DevicesProfileTest, Delete) {
TestingProfile profile;
cablev2::AddPairing(profile.GetPrefs(), PairingWithAllFields(), {});
cablev2::DeletePairingByPublicKey(
profile.GetPrefs(), PairingWithAllFields()->peer_public_key_x962);
std::unique_ptr<KnownDevices> known_devices =
KnownDevices::FromProfile(&profile);
EXPECT_EQ(known_devices->synced_devices.size(), 0u);
EXPECT_EQ(known_devices->linked_devices.size(), 0u);
}
TEST_F(CableV2DevicesProfileTest, NameFiltering) {
// The pairing name is stored as sent by the device, but any newlines are
// removed when reading so that they don't mess up the UI.
TestingProfile profile;
cablev2::AddPairing(profile.GetPrefs(),
LinkedDevice("w\nx\x0by\xe2\x80\xa8z", kPubKey1), {});
std::unique_ptr<KnownDevices> known_devices =
KnownDevices::FromProfile(&profile);
EXPECT_EQ(known_devices->synced_devices.size(), 0u);
ASSERT_EQ(known_devices->linked_devices.size(), 1u);
EXPECT_EQ(known_devices->linked_devices[0]->name, "wxyz");
}
TEST_F(CableV2DevicesProfileTest, NameCollision) {
// Adding a device that has a name equal to one of the existing names should
// cause the name to be changed to something that doesn't collide.
TestingProfile profile;
const base::StringPiece kExistingNames[] = {"collision", "collision (1)",
"collision (2)\n"};
cablev2::AddPairing(profile.GetPrefs(), LinkedDevice("collision", kPubKey1),
kExistingNames);
std::unique_ptr<KnownDevices> known_devices =
KnownDevices::FromProfile(&profile);
EXPECT_EQ(known_devices->synced_devices.size(), 0u);
ASSERT_EQ(known_devices->linked_devices.size(), 1u);
EXPECT_EQ(known_devices->linked_devices[0]->name, "collision (3)");
}
TEST_F(CableV2DevicesProfileTest, Rename) {
TestingProfile profile;
PrefService* const prefs = profile.GetPrefs();
cablev2::AddPairing(prefs, LinkedDevice("one", kPubKey1), {});
cablev2::AddPairing(prefs, LinkedDevice("two", kPubKey2), {});
cablev2::AddPairing(prefs, LinkedDevice("three", kPubKey3), {});
std::unique_ptr<KnownDevices> known_devices =
KnownDevices::FromProfile(&profile);
std::array<uint8_t, device::kP256X962Length> public_key = {0};
public_key[0] = kPubKey2;
// Since a device with the name "three" already exists, the new name should
// be mapped to "three (1)" to avoid it.
EXPECT_TRUE(cablev2::RenamePairing(prefs, public_key, "three",
known_devices->Names()));
known_devices = KnownDevices::FromProfile(&profile);
EXPECT_EQ(known_devices->synced_devices.size(), 0u);
ASSERT_EQ(known_devices->linked_devices.size(), 3u);
std::sort(known_devices->linked_devices.begin(),
known_devices->linked_devices.end(),
[](auto&& a, auto&& b) -> bool { return a->name < b->name; });
EXPECT_EQ(known_devices->linked_devices[0]->name, "one");
EXPECT_EQ(known_devices->linked_devices[1]->name, "three");
EXPECT_EQ(known_devices->linked_devices[2]->name, "three (1)");
public_key[0] = kPubKey4;
// This shouldn't do anything and should return false because the public key
// is unknown.
EXPECT_FALSE(cablev2::RenamePairing(prefs, public_key, "three",
known_devices->Names()));
}
} // namespace