blob: ee89079043503f8837ec8872b1080b1f88af24c8 [file] [log] [blame]
// Copyright 2018 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 "device/fido/fido_cable_discovery.h"
#include <algorithm>
#include <memory>
#include <utility>
#include "base/stl_util.h"
#include "build/build_config.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/test/bluetooth_test.h"
#include "device/bluetooth/test/mock_bluetooth_adapter.h"
#include "device/bluetooth/test/mock_bluetooth_advertisement.h"
#include "device/fido/fido_ble_device.h"
#include "device/fido/fido_ble_uuids.h"
#include "device/fido/fido_parsing_utils.h"
#include "device/fido/mock_fido_discovery_observer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::NiceMock;
namespace device {
namespace {
// Constants required for discovering and constructing a Cable device that
// are given by the relying party via an extension.
constexpr FidoCableDiscovery::EidArray kClientEid = {
{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11,
0x12, 0x13, 0x14, 0x15}};
constexpr char kUuidFormattedClientEid[] =
"00010203-0405-0607-0809-101112131415";
constexpr FidoCableDiscovery::EidArray kAuthenticatorEid = {
{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01}};
constexpr FidoCableDiscovery::EidArray kInvalidAuthenticatorEid = {
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00}};
constexpr FidoCableDiscovery::SessionKeyArray kTestSessionKey = {
{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}};
constexpr FidoCableDiscovery::EidArray kSecondaryClientEid = {
{0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04,
0x03, 0x02, 0x01, 0x00}};
constexpr char kUuidFormattedSecondaryClientEid[] =
"15141312-1110-0908-0706-050403020100";
constexpr FidoCableDiscovery::EidArray kSecondaryAuthenticatorEid = {
{0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee,
0xee, 0xee, 0xee, 0xee}};
constexpr FidoCableDiscovery::SessionKeyArray kSecondarySessionKey = {
{0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd}};
// Below constants are used to construct MockBluetoothDevice for testing.
constexpr char kTestBleDeviceAddress[] = "11:12:13:14:15:16";
constexpr char kTestBleDeviceName[] = "test_cable_device";
std::unique_ptr<MockBluetoothDevice> CreateTestBluetoothDevice() {
return std::make_unique<testing::NiceMock<MockBluetoothDevice>>(
nullptr /* adapter */, 0 /* bluetooth_class */, kTestBleDeviceName,
kTestBleDeviceAddress, true /* paired */, true /* connected */);
}
// Matcher to compare the content of advertisement data received from the
// client.
MATCHER_P2(IsAdvertisementContent,
expected_client_eid,
expected_uuid_formatted_client_eid,
"") {
#if defined(OS_MACOSX)
const auto uuid_list = arg->service_uuids();
return std::any_of(uuid_list->begin(), uuid_list->end(),
[this](const auto& uuid) {
return uuid == expected_uuid_formatted_client_eid;
});
#elif defined(OS_WIN)
const auto manufacturer_data = arg->manufacturer_data();
const auto manufacturer_data_value = manufacturer_data->find(0xFFFD);
if (manufacturer_data_value == manufacturer_data->end())
return false;
return fido_parsing_utils::ExtractSuffixSpan(manufacturer_data_value->second,
2) == expected_client_eid;
#elif defined(OS_LINUX) || defined(OS_CHROMEOS)
const auto service_data = arg->service_data();
const auto service_data_with_uuid =
service_data->find(kCableAdvertisementUUID);
return service_data_with_uuid != service_data->end() &&
base::make_span(service_data_with_uuid->second) == expected_client_eid;
#endif
return true;
}
// Mock BLE adapter that abstracts out authenticator logic with the following
// logic:
// - Responds to BluetoothAdapter::RegisterAdvertisement() by always invoking
// success callback.
// - Responds to BluetoothAdapter::StartDiscoverySessionWithFilter() by
// invoking BluetoothAdapter::Observer::DeviceAdded() on a test bluetooth
// device that includes service data containing authenticator EID.
class CableMockAdapter : public MockBluetoothAdapter {
public:
MOCK_METHOD3(RegisterAdvertisement,
void(std::unique_ptr<BluetoothAdvertisement::Data>,
const CreateAdvertisementCallback&,
const AdvertisementErrorCallback&));
void AddNewTestBluetoothDevice(
base::span<const uint8_t, FidoCableDiscovery::kEphemeralIdSize>
authenticator_eid) {
auto mock_device = CreateTestBluetoothDevice();
std::vector<uint8_t> service_data(8);
fido_parsing_utils::Append(&service_data, authenticator_eid);
BluetoothDevice::ServiceDataMap service_data_map;
service_data_map.emplace(kCableAdvertisementUUID, std::move(service_data));
mock_device->UpdateAdvertisementData(
1 /* rssi */, BluetoothDevice::UUIDList(), std::move(service_data_map),
BluetoothDevice::ManufacturerDataMap(), nullptr /* tx_power*/);
auto* mock_device_ptr = mock_device.get();
AddMockDevice(std::move(mock_device));
for (auto& observer : GetObservers())
observer.DeviceAdded(this, mock_device_ptr);
}
void ExpectRegisterAdvertisementWithResponse(
bool simulate_success,
base::span<const uint8_t> expected_client_eid,
base::StringPiece expected_uuid_formatted_client_eid) {
EXPECT_CALL(*this,
RegisterAdvertisement(
IsAdvertisementContent(expected_client_eid,
expected_uuid_formatted_client_eid),
_, _))
.WillOnce(::testing::WithArgs<1, 2>(
[simulate_success](const auto& success_callback,
const auto& failure_callback) {
simulate_success
? success_callback.Run(
base::MakeRefCounted<MockBluetoothAdvertisement>())
: failure_callback.Run(BluetoothAdvertisement::ErrorCode::
INVALID_ADVERTISEMENT_ERROR_CODE);
}));
}
void ExpectSuccessCallbackToSetPowered() {
EXPECT_CALL(*this, SetPowered(true, _, _))
.WillOnce(::testing::WithArg<1>(
[](const auto& callback) { callback.Run(); }));
}
protected:
~CableMockAdapter() override = default;
};
} // namespace
class FidoCableDiscoveryTest : public ::testing::Test {
public:
std::unique_ptr<FidoCableDiscovery> CreateDiscovery() {
std::vector<FidoCableDiscovery::CableDiscoveryData> discovery_data;
discovery_data.emplace_back(kClientEid, kAuthenticatorEid, kTestSessionKey);
return std::make_unique<FidoCableDiscovery>(std::move(discovery_data));
}
base::test::ScopedTaskEnvironment scoped_task_environment_;
};
// Tests regular successful discovery flow for Cable device.
TEST_F(FidoCableDiscoveryTest, TestDiscoveryFindsNewDevice) {
auto cable_discovery = CreateDiscovery();
NiceMock<MockFidoDiscoveryObserver> mock_observer;
EXPECT_CALL(mock_observer, DeviceAdded(_, _));
cable_discovery->set_observer(&mock_observer);
auto mock_adapter =
base::MakeRefCounted<::testing::NiceMock<CableMockAdapter>>();
::testing::InSequence testing_sequence;
mock_adapter->ExpectSuccessCallbackToSetPowered();
mock_adapter->ExpectRegisterAdvertisementWithResponse(
true /* simulate_success */, kClientEid, kUuidFormattedClientEid);
EXPECT_CALL(*mock_adapter, StartDiscoverySessionWithFilterRaw(_, _, _))
.WillOnce(::testing::WithArg<1>([&mock_adapter](const auto& callback) {
mock_adapter->AddNewTestBluetoothDevice(kAuthenticatorEid);
callback.Run(nullptr);
}));
BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter);
cable_discovery->Start();
scoped_task_environment_.RunUntilIdle();
}
// Tests a scenario where upon broadcasting advertisement and scanning, client
// discovers a device with an incorrect authenticator EID. Observer::AddDevice()
// must not be called.
TEST_F(FidoCableDiscoveryTest, TestDiscoveryFindsIncorrectDevice) {
auto cable_discovery = CreateDiscovery();
NiceMock<MockFidoDiscoveryObserver> mock_observer;
EXPECT_CALL(mock_observer, DeviceAdded(_, _)).Times(0);
cable_discovery->set_observer(&mock_observer);
auto mock_adapter =
base::MakeRefCounted<::testing::NiceMock<CableMockAdapter>>();
::testing::InSequence testing_sequence;
mock_adapter->ExpectSuccessCallbackToSetPowered();
mock_adapter->ExpectRegisterAdvertisementWithResponse(
true /* simulate_success */, kClientEid, kUuidFormattedClientEid);
EXPECT_CALL(*mock_adapter, StartDiscoverySessionWithFilterRaw(_, _, _))
.WillOnce(::testing::WithArg<1>([&mock_adapter](const auto& callback) {
mock_adapter->AddNewTestBluetoothDevice(kInvalidAuthenticatorEid);
callback.Run(nullptr);
}));
BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter);
cable_discovery->Start();
scoped_task_environment_.RunUntilIdle();
}
// Tests Cable discovery flow when multiple(2) sets of client/authenticator EIDs
// are passed on from the relying party. We should expect 2 invocations of
// BluetoothAdapter::RegisterAdvertisement().
TEST_F(FidoCableDiscoveryTest, TestDiscoveryWithMultipleEids) {
std::vector<FidoCableDiscovery::CableDiscoveryData> discovery_data;
discovery_data.emplace_back(kClientEid, kAuthenticatorEid, kTestSessionKey);
discovery_data.emplace_back(kSecondaryClientEid, kSecondaryAuthenticatorEid,
kSecondarySessionKey);
auto cable_discovery =
std::make_unique<FidoCableDiscovery>(std::move(discovery_data));
NiceMock<MockFidoDiscoveryObserver> mock_observer;
EXPECT_CALL(mock_observer, DeviceAdded(_, _));
cable_discovery->set_observer(&mock_observer);
auto mock_adapter =
base::MakeRefCounted<::testing::NiceMock<CableMockAdapter>>();
::testing::InSequence testing_sequence;
mock_adapter->ExpectSuccessCallbackToSetPowered();
mock_adapter->ExpectRegisterAdvertisementWithResponse(
true /* simulate_success */, kClientEid, kUuidFormattedClientEid);
mock_adapter->ExpectRegisterAdvertisementWithResponse(
true /* simulate_success */, kSecondaryClientEid,
kUuidFormattedSecondaryClientEid);
EXPECT_CALL(*mock_adapter, StartDiscoverySessionWithFilterRaw(_, _, _))
.WillOnce(::testing::WithArg<1>([&mock_adapter](const auto& callback) {
mock_adapter->AddNewTestBluetoothDevice(kAuthenticatorEid);
callback.Run(nullptr);
}));
BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter);
cable_discovery->Start();
scoped_task_environment_.RunUntilIdle();
}
// Tests a scenario where only one of the two client EID's are advertised
// successfully. Since at least one advertisement are successfully processed,
// scanning process should be invoked.
TEST_F(FidoCableDiscoveryTest, TestDiscoveryWithPartialAdvertisementSuccess) {
std::vector<FidoCableDiscovery::CableDiscoveryData> discovery_data;
discovery_data.emplace_back(kClientEid, kAuthenticatorEid, kTestSessionKey);
discovery_data.emplace_back(kSecondaryClientEid, kSecondaryAuthenticatorEid,
kSecondarySessionKey);
auto cable_discovery =
std::make_unique<FidoCableDiscovery>(std::move(discovery_data));
NiceMock<MockFidoDiscoveryObserver> mock_observer;
EXPECT_CALL(mock_observer, DeviceAdded(_, _));
cable_discovery->set_observer(&mock_observer);
auto mock_adapter =
base::MakeRefCounted<::testing::NiceMock<CableMockAdapter>>();
::testing::InSequence testing_sequence;
mock_adapter->ExpectSuccessCallbackToSetPowered();
mock_adapter->ExpectRegisterAdvertisementWithResponse(
true /* simulate_success */, kClientEid, kUuidFormattedClientEid);
mock_adapter->ExpectRegisterAdvertisementWithResponse(
false /* simulate_success */, kSecondaryClientEid,
kUuidFormattedSecondaryClientEid);
EXPECT_CALL(*mock_adapter, StartDiscoverySessionWithFilterRaw(_, _, _))
.WillOnce(::testing::WithArg<1>([&mock_adapter](const auto& callback) {
mock_adapter->AddNewTestBluetoothDevice(kAuthenticatorEid);
callback.Run(nullptr);
}));
BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter);
cable_discovery->Start();
scoped_task_environment_.RunUntilIdle();
}
// Test the scenario when all advertisement for client EID's fails.
TEST_F(FidoCableDiscoveryTest, TestDiscoveryWithAdvertisementFailures) {
std::vector<FidoCableDiscovery::CableDiscoveryData> discovery_data;
discovery_data.emplace_back(kClientEid, kAuthenticatorEid, kTestSessionKey);
discovery_data.emplace_back(kSecondaryClientEid, kSecondaryAuthenticatorEid,
kSecondarySessionKey);
auto cable_discovery =
std::make_unique<FidoCableDiscovery>(std::move(discovery_data));
NiceMock<MockFidoDiscoveryObserver> mock_observer;
EXPECT_CALL(mock_observer, DeviceAdded(_, _)).Times(0);
cable_discovery->set_observer(&mock_observer);
auto mock_adapter =
base::MakeRefCounted<::testing::NiceMock<CableMockAdapter>>();
::testing::InSequence testing_sequence;
mock_adapter->ExpectSuccessCallbackToSetPowered();
mock_adapter->ExpectRegisterAdvertisementWithResponse(
false /* simulate_success */, kClientEid, kUuidFormattedClientEid);
mock_adapter->ExpectRegisterAdvertisementWithResponse(
false /* simulate_success */, kSecondaryClientEid,
kUuidFormattedSecondaryClientEid);
EXPECT_CALL(*mock_adapter, StartDiscoverySessionWithFilterRaw(_, _, _))
.Times(0);
BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter);
cable_discovery->Start();
scoped_task_environment_.RunUntilIdle();
}
} // namespace device