Commit 4ab25391 authored by jdoerrie's avatar jdoerrie Committed by Commit Bot

[fido] Add FidoBleDevice::IsInPairingMode()

This change introduces the possibility to query a FidoBleDevice whether
or not it is currently in pairing mode. In accordance with the CTAP spec
this is done by looking at the advertisment data flags if possible, and
falling back to investigating service data if required.

Bug: 763303
Change-Id: I373151a10d63e83ca2ac57f7b53c1269b9d60cfc
Reviewed-on: https://chromium-review.googlesource.com/1096234Reviewed-by: default avatarGiovanni Ortuño Urquidi <ortuno@chromium.org>
Reviewed-by: default avatarJames Hawkins <jhawkins@chromium.org>
Reviewed-by: default avatarBalazs Engedy <engedy@chromium.org>
Commit-Queue: Jan Wilken Dörrie <jdoerrie@chromium.org>
Cr-Commit-Position: refs/heads/master@{#569929}
parent ad84abf0
......@@ -206,8 +206,9 @@ class ProximityAuthBluetoothLowEnergyConnectionFinderTest
uuid_list.push_back(advertisement_uuid);
device::BluetoothDevice::ServiceDataMap service_data_map;
service_data_map[advertisement_uuid] = eid_vector;
device_->UpdateAdvertisementData(kRssi, uuid_list, service_data_map,
{} /* manufacturer_data */, nullptr);
device_->UpdateAdvertisementData(
kRssi, uuid_list, service_data_map, {} /* manufacturer_data */,
nullptr /* tx_power */, nullptr /* flags */);
}
scoped_refptr<device::MockBluetoothAdapter> adapter_;
......
......@@ -238,7 +238,7 @@ void BluetoothAdapterAndroid::CreateOrUpdateDeviceOnScan(
service_data_map, manufacturer_data_map,
// Android uses INT32_MIN to indicate no Advertised Tx Power.
// https://developer.android.com/reference/android/bluetooth/le/ScanRecord.html#getTxPowerLevel()
tx_power == INT32_MIN ? nullptr : &clamped_tx_power);
tx_power == INT32_MIN ? nullptr : &clamped_tx_power, nullptr /* flags */);
if (is_new_device) {
devices_[device_address] = std::move(device_android_owner);
......
......@@ -623,7 +623,7 @@ void BluetoothAdapterMac::LowEnergyDeviceUpdated(
device_mac->UpdateAdvertisementData(
BluetoothDevice::ClampPower(rssi), std::move(advertised_uuids),
std::move(service_data_map), std::move(manufacturer_data_map),
tx_power == nil ? nullptr : &clamped_tx_power);
tx_power == nil ? nullptr : &clamped_tx_power, nullptr /* flags */);
if (is_new_device) {
std::string device_address =
......
......@@ -175,9 +175,10 @@ void ExtractAndUpdateAdvertisementData(
// TODO(https://crbug.com/821766): Implement extraction of service data,
// manufacturer data and tx power.
device->UpdateAdvertisementData(
rssi, std::move(*advertised_uuids), BluetoothDevice::ServiceDataMap(),
BluetoothDevice::ManufacturerDataMap(), nullptr /* tx_power */);
device->UpdateAdvertisementData(rssi, std::move(*advertised_uuids),
BluetoothDevice::ServiceDataMap(),
BluetoothDevice::ManufacturerDataMap(),
nullptr /* tx_power */, nullptr /* flags */);
}
} // namespace
......
......@@ -7,6 +7,7 @@
#include <iterator>
#include <memory>
#include <string>
#include <utility>
#include "base/memory/ptr_util.h"
#include "base/strings/string_util.h"
......@@ -418,7 +419,8 @@ void BluetoothDevice::UpdateAdvertisementData(
UUIDList advertised_uuids,
ServiceDataMap service_data,
ManufacturerDataMap manufacturer_data,
const int8_t* tx_power) {
const int8_t* tx_power,
const uint8_t* flags) {
UpdateTimestamp();
inquiry_rssi_ = rssi;
......@@ -432,6 +434,12 @@ void BluetoothDevice::UpdateAdvertisementData(
} else {
inquiry_tx_power_ = base::nullopt;
}
if (flags != nullptr) {
advertising_data_flags_ = *flags;
} else {
advertising_data_flags_.reset();
}
}
void BluetoothDevice::ClearAdvertisementData() {
......
......@@ -548,7 +548,8 @@ class DEVICE_BLUETOOTH_EXPORT BluetoothDevice {
UUIDList advertised_uuids,
ServiceDataMap service_data,
ManufacturerDataMap manufacturer_data,
const int8_t* tx_power);
const int8_t* tx_power,
const uint8_t* flags);
// Called by BluetoothAdapter when it stops discoverying.
void ClearAdvertisementData();
......
......@@ -94,7 +94,8 @@ void FakeCentral::SimulateAdvertisementReceived(
std::move(manufacturer_data),
(scan_result_ptr->scan_record->tx_power->has_value)
? &scan_result_ptr->scan_record->tx_power->value
: nullptr);
: nullptr,
nullptr /* flags */);
if (is_new_device) {
// Call DeviceAdded on observers because it is a newly detected peripheral.
......
......@@ -91,6 +91,10 @@ FidoBleConnection::~FidoBleConnection() {
adapter_->RemoveObserver(this);
}
const BluetoothDevice* FidoBleConnection::GetBleDevice() const {
return adapter_ ? adapter_->GetDevice(address()) : nullptr;
}
void FidoBleConnection::Connect() {
BluetoothAdapterFactory::GetAdapter(
base::Bind(&FidoBleConnection::OnGetAdapter, weak_factory_.GetWeakPtr()));
......
......@@ -68,6 +68,7 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleConnection
~FidoBleConnection() override;
const std::string& address() const { return address_; }
const BluetoothDevice* GetBleDevice() const;
virtual void Connect();
virtual void ReadControlPointLength(ControlPointLengthCallback callback);
......@@ -80,6 +81,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleConnection
protected:
explicit FidoBleConnection(std::string device_address);
std::string address_;
ConnectionStatusCallback connection_status_callback_;
ReadCallback read_callback_;
private:
// BluetoothAdapter::Observer:
void DeviceAdded(BluetoothAdapter* adapter, BluetoothDevice* device) override;
......@@ -137,10 +142,6 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleConnection
static void OnWriteError(WriteCallback callback,
BluetoothGattService::GattErrorCode error_code);
std::string address_;
ConnectionStatusCallback connection_status_callback_;
ReadCallback read_callback_;
scoped_refptr<BluetoothAdapter> adapter_;
std::unique_ptr<BluetoothGattConnection> connection_;
std::unique_ptr<BluetoothGattNotifySession> notify_session_;
......
......@@ -4,10 +4,14 @@
#include "device/fido/fido_ble_device.h"
#include <bitset>
#include "base/bind.h"
#include "base/strings/string_piece.h"
#include "components/apdu/apdu_response.h"
#include "device/bluetooth/bluetooth_uuid.h"
#include "device/fido/fido_ble_frames.h"
#include "device/fido/fido_ble_uuids.h"
#include "device/fido/fido_constants.h"
namespace device {
......@@ -63,6 +67,35 @@ std::string FidoBleDevice::GetId() const {
return GetId(connection_->address());
}
bool FidoBleDevice::IsInPairingMode() const {
const BluetoothDevice* const ble_device = connection_->GetBleDevice();
if (!ble_device)
return false;
// The spec requires exactly one of the LE Limited Discoverable Mode and LE
// General Discoverable Mode bits to be set to one when in pairing mode.
// https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#ble-advertising-format
const base::Optional<uint8_t> flags = ble_device->GetAdvertisingDataFlags();
if (flags.has_value()) {
const std::bitset<8> flags_set = *flags;
return flags_set[kLeLimitedDiscoverableModeBit] ^
flags_set[kLeGeneralDiscoverableModeBit];
}
// Since the advertisement flags might not be available due to platform
// limitations, authenticators should also provide a specific pairing mode bit
// in FIDO's service data.
// https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#ble-pairing-authnr-considerations
const std::vector<uint8_t>* const fido_service_data =
ble_device->GetServiceDataForUUID(BluetoothUUID(kFidoServiceUUID));
if (!fido_service_data)
return false;
return !fido_service_data->empty() &&
(fido_service_data->front() &
static_cast<int>(FidoServiceDataFlags::kPairingMode)) != 0;
}
FidoBleConnection::ConnectionStatusCallback
FidoBleDevice::GetConnectionStatusCallbackForTesting() {
return base::BindRepeating(&FidoBleDevice::OnConnectionStatus,
......
......@@ -42,6 +42,10 @@ class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleDevice : public FidoDevice {
void Cancel() override;
std::string GetId() const override;
// Returns whether or not the underlying BLE device is currently in pairing
// mode by investigating the advertisement payload.
bool IsInPairingMode() const;
FidoBleConnection::ConnectionStatusCallback
GetConnectionStatusCallbackForTesting();
FidoBleConnection::ReadCallback GetReadCallbackForTesting();
......
......@@ -7,7 +7,11 @@
#include "base/optional.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_task_environment.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_device.h"
#include "device/fido/fido_ble_uuids.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_parsing_utils.h"
#include "device/fido/mock_fido_ble_connection.h"
......@@ -24,6 +28,9 @@ using ::testing::Test;
using TestDeviceCallbackReceiver =
test::ValueCallbackReceiver<base::Optional<std::vector<uint8_t>>>;
using NiceMockBluetoothAdapter = ::testing::NiceMock<MockBluetoothAdapter>;
using NiceMockBluetoothDevice = ::testing::NiceMock<MockBluetoothDevice>;
constexpr uint16_t kControlPointLength = 20;
constexpr uint8_t kTestData[] = {'T', 'E', 'S', 'T'};
......@@ -46,6 +53,23 @@ std::vector<std::vector<uint8_t>> ToSerializedFragments(
return serialized_fragments;
}
void SetAdvertisingDataFlags(BluetoothDevice* device,
base::Optional<uint8_t> flags) {
device->UpdateAdvertisementData(
/* rssi */ 0, BluetoothDevice::UUIDList(),
BluetoothDevice::ServiceDataMap(), BluetoothDevice::ManufacturerDataMap(),
/* tx_power */ nullptr, base::OptionalOrNullptr(flags));
}
void SetServiceData(BluetoothDevice* device,
BluetoothDevice::ServiceDataMap service_data) {
device->UpdateAdvertisementData(
/* rssi */ 0, BluetoothDevice::UUIDList(), std::move(service_data),
BluetoothDevice::ManufacturerDataMap(),
/* tx_power */ nullptr,
/* flags */ nullptr);
}
} // namespace
class FidoBleDeviceTest : public Test {
......@@ -196,4 +220,68 @@ TEST_F(FidoBleDeviceTest, GetIdTest) {
device()->GetId());
}
TEST_F(FidoBleDeviceTest, IsInPairingMode) {
// By default, a device is not in pairing mode.
EXPECT_FALSE(device()->IsInPairingMode());
// Initiate default connection behavior, which will attempt to obtain an
// adapter.
auto mock_adapter = base::MakeRefCounted<NiceMockBluetoothAdapter>();
BluetoothAdapterFactory::SetAdapterForTesting(mock_adapter);
EXPECT_CALL(*connection(), Connect()).WillOnce(Invoke([this] {
connection()->FidoBleConnection::Connect();
}));
device()->Connect();
// Add a mock fido device. This should also not be considered to be in pairing
// mode.
auto mock_bluetooth_device = std::make_unique<NiceMockBluetoothDevice>(
mock_adapter.get(), /* bluetooth_class */ 0u,
BluetoothTestBase::kTestDeviceNameU2f,
BluetoothTestBase::kTestDeviceAddress1, /* paired */ true,
/* connected */ false);
EXPECT_CALL(*mock_adapter, GetDevice(BluetoothTestBase::kTestDeviceAddress1))
.WillRepeatedly(Return(mock_bluetooth_device.get()));
EXPECT_FALSE(device()->IsInPairingMode());
// Provide advertisement flags, but set neither the Limited nor General LE
// Discoverable Mode bit.
SetAdvertisingDataFlags(mock_bluetooth_device.get(), 0);
EXPECT_FALSE(device()->IsInPairingMode());
// Set the limited bit, expect to be in pairing mode.
SetAdvertisingDataFlags(mock_bluetooth_device.get(),
1 << kLeLimitedDiscoverableModeBit);
EXPECT_TRUE(device()->IsInPairingMode());
// Set the general bit, expect to be in pairing mode.
SetAdvertisingDataFlags(mock_bluetooth_device.get(),
1 << kLeGeneralDiscoverableModeBit);
EXPECT_TRUE(device()->IsInPairingMode());
// Set both bits, expect to be NOT in pairing mode.
SetAdvertisingDataFlags(
mock_bluetooth_device.get(),
1 << kLeLimitedDiscoverableModeBit | 1 << kLeGeneralDiscoverableModeBit);
EXPECT_FALSE(device()->IsInPairingMode());
// Remove flags, should not result in pairing mode.
SetAdvertisingDataFlags(mock_bluetooth_device.get(), base::nullopt);
EXPECT_FALSE(device()->IsInPairingMode());
// Update the advertised service data to include the corresponding pairing
// mode flag. This should result in the device to be considered in pairing
// mode.
SetServiceData(mock_bluetooth_device.get(),
{{BluetoothUUID(kFidoServiceUUID),
{static_cast<int>(FidoServiceDataFlags::kPairingMode)}}});
EXPECT_TRUE(device()->IsInPairingMode());
// Clear out the service data again, device should not be considered to be in
// pairing mode anymore.
SetServiceData(mock_bluetooth_device.get(), {});
EXPECT_FALSE(device()->IsInPairingMode());
}
} // namespace device
......@@ -161,7 +161,8 @@ class CableMockAdapter : public MockBluetoothAdapter {
mock_device->UpdateAdvertisementData(
1 /* rssi */, BluetoothDevice::UUIDList(), std::move(service_data_map),
BluetoothDevice::ManufacturerDataMap(), nullptr /* tx_power*/);
BluetoothDevice::ManufacturerDataMap(), nullptr /* tx_power*/,
nullptr /* flags */);
auto* mock_device_ptr = mock_device.get();
AddMockDevice(std::move(mock_device));
......
......@@ -222,6 +222,18 @@ enum class FidoBleDeviceCommand : uint8_t {
kError = 0xBF,
};
// Relevant LE Discoverable Mode bits. Reference:
// Bluetooth Core Specification Supplement, Part A, section 1.3
constexpr uint8_t kLeLimitedDiscoverableModeBit = 0;
constexpr uint8_t kLeGeneralDiscoverableModeBit = 1;
// Fido Service Data Flags as specified in
// https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#ble-pairing-authnr-considerations
enum class FidoServiceDataFlags : uint8_t {
kPairingMode = 0x80,
kPasskeyEntry = 0x40,
};
// Authenticator API commands supported by CTAP devices, as specified in
// https://fidoalliance.org/specs/fido-v2.0-rd-20170927/fido-client-to-authenticator-protocol-v2.0-rd-20170927.html#authenticator-api
enum class CtapRequestCommand : uint8_t {
......
......@@ -45,9 +45,6 @@ class MockFidoBleConnection : public FidoBleConnection {
ReadCallback& read_callback() { return read_callback_; }
private:
ConnectionStatusCallback connection_status_callback_;
ReadCallback read_callback_;
DISALLOW_COPY_AND_ASSIGN(MockFidoBleConnection);
};
......
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