blob: 5911dda11f3e5b84589e1a37d8ffdcf4ef1d1538 [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/bind.h"
#include "base/bind_helpers.h"
#include "base/callback_helpers.h"
#include "base/strings/stringprintf.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "build/build_config.h"
#include "device/bluetooth/bluetooth_advertisement.h"
#include "device/bluetooth/bluetooth_discovery_session.h"
#include "device/bluetooth/bluetooth_uuid.h"
#include "device/fido/fido_ble_uuids.h"
#include "device/fido/fido_cable_device.h"
#include "device/fido/fido_cable_handshake_handler.h"
#include "device/fido/fido_parsing_utils.h"
namespace device {
namespace {
#if defined(OS_MACOSX)
// Convert byte array into GUID formatted string as defined by RFC 4122.
// As we are converting 128 bit UUID, |bytes| must be have length of 16.
// https://tools.ietf.org/html/rfc4122
std::string ConvertBytesToUuid(base::span<const uint8_t, 16> bytes) {
uint64_t most_significant_bytes = 0;
for (size_t i = 0; i < sizeof(uint64_t); i++) {
most_significant_bytes |= base::strict_cast<uint64_t>(bytes[i])
<< 8 * (7 - i);
}
uint64_t least_significant_bytes = 0;
for (size_t i = 0; i < sizeof(uint64_t); i++) {
least_significant_bytes |= base::strict_cast<uint64_t>(bytes[i + 8])
<< 8 * (7 - i);
}
return base::StringPrintf(
"%08x-%04x-%04x-%04x-%012llx",
static_cast<unsigned int>(most_significant_bytes >> 32),
static_cast<unsigned int>((most_significant_bytes >> 16) & 0x0000ffff),
static_cast<unsigned int>(most_significant_bytes & 0x0000ffff),
static_cast<unsigned int>(least_significant_bytes >> 48),
least_significant_bytes & 0x0000ffff'ffffffffULL);
}
#endif
const BluetoothUUID& CableAdvertisementUUID() {
static const BluetoothUUID service_uuid(kCableAdvertisementUUID);
return service_uuid;
}
bool IsCableDevice(const BluetoothDevice* device) {
return base::ContainsKey(device->GetServiceData(), CableAdvertisementUUID());
}
// Construct advertisement data with different formats depending on client's
// operating system. Ideally, we advertise EIDs as part of Service Data, but
// this isn't available on all platforms. On Windows we use Manufacturer Data
// instead, and on Mac our only option is to advertise an additional service
// with the EID as its UUID.
std::unique_ptr<BluetoothAdvertisement::Data> ConstructAdvertisementData(
uint8_t version_number,
base::span<const uint8_t, FidoCableDiscovery::kEphemeralIdSize>
client_eid) {
auto advertisement_data = std::make_unique<BluetoothAdvertisement::Data>(
BluetoothAdvertisement::AdvertisementType::ADVERTISEMENT_TYPE_BROADCAST);
#if defined(OS_MACOSX)
auto list = std::make_unique<BluetoothAdvertisement::UUIDList>();
list->emplace_back(kCableAdvertisementUUID);
list->emplace_back(ConvertBytesToUuid(client_eid));
advertisement_data->set_service_uuids(std::move(list));
#elif defined(OS_WIN)
constexpr uint16_t kFidoManufacturerId = 0xFFFD;
constexpr std::array<uint8_t, 2> kFidoManufacturerDataHeader = {0x51, 0xFE};
auto manufacturer_data =
std::make_unique<BluetoothAdvertisement::ManufacturerData>();
std::vector<uint8_t> manufacturer_data_value;
fido_parsing_utils::Append(&manufacturer_data_value,
kFidoManufacturerDataHeader);
fido_parsing_utils::Append(&manufacturer_data_value, client_eid);
manufacturer_data->emplace(kFidoManufacturerId,
std::move(manufacturer_data_value));
advertisement_data->set_manufacturer_data(std::move(manufacturer_data));
#elif defined(OS_LINUX) || defined(OS_CHROMEOS)
// Service data for ChromeOS and Linux is 1 byte corresponding to Cable flags,
// followed by 1 byte corresponding to Cable version number, followed by 16
// bytes corresponding to client EID.
auto service_data = std::make_unique<BluetoothAdvertisement::ServiceData>();
std::vector<uint8_t> service_data_value(18, 0);
// Since the remainder of this service data field is a Cable EID, set the 5th
// bit of the flag byte.
service_data_value[0] = 1 << 5;
service_data_value[1] = version_number;
std::copy(client_eid.begin(), client_eid.end(),
service_data_value.begin() + 2);
service_data->emplace(kCableAdvertisementUUID, std::move(service_data_value));
advertisement_data->set_service_data(std::move(service_data));
#endif
return advertisement_data;
}
} // namespace
// FidoCableDiscovery::CableDiscoveryData -------------------------------------
FidoCableDiscovery::CableDiscoveryData::CableDiscoveryData(
uint8_t version,
const EidArray& client_eid,
const EidArray& authenticator_eid,
const SessionPreKeyArray& session_pre_key)
: version(version),
client_eid(client_eid),
authenticator_eid(authenticator_eid),
session_pre_key(session_pre_key) {}
FidoCableDiscovery::CableDiscoveryData::CableDiscoveryData(
const CableDiscoveryData& data) = default;
FidoCableDiscovery::CableDiscoveryData& FidoCableDiscovery::CableDiscoveryData::
operator=(const CableDiscoveryData& other) = default;
FidoCableDiscovery::CableDiscoveryData::~CableDiscoveryData() = default;
// FidoCableDiscovery ---------------------------------------------------------
FidoCableDiscovery::FidoCableDiscovery(
std::vector<CableDiscoveryData> discovery_data)
: discovery_data_(std::move(discovery_data)), weak_factory_(this) {}
// This is a workaround for https://crbug.com/846522
FidoCableDiscovery::~FidoCableDiscovery() {
for (auto advertisement : advertisements_)
advertisement.second->Unregister(base::DoNothing(), base::DoNothing());
}
std::unique_ptr<FidoCableHandshakeHandler>
FidoCableDiscovery::CreateHandshakeHandler(
FidoCableDevice* device,
base::span<const uint8_t, kSessionPreKeySize> session_pre_key,
base::span<const uint8_t, 8> nonce) {
return std::make_unique<FidoCableHandshakeHandler>(device, nonce,
session_pre_key);
}
void FidoCableDiscovery::DeviceAdded(BluetoothAdapter* adapter,
BluetoothDevice* device) {
if (!IsCableDevice(device))
return;
DVLOG(2) << "Discovered Cable device: " << device->GetAddress();
CableDeviceFound(adapter, device);
}
void FidoCableDiscovery::DeviceChanged(BluetoothAdapter* adapter,
BluetoothDevice* device) {
if (!IsCableDevice(device))
return;
DVLOG(2) << "Device changed for Cable device: " << device->GetAddress();
CableDeviceFound(adapter, device);
}
void FidoCableDiscovery::DeviceRemoved(BluetoothAdapter* adapter,
BluetoothDevice* device) {
if (IsCableDevice(device) && GetFoundCableDiscoveryData(device)) {
const auto& device_address = device->GetAddress();
VLOG(2) << "Cable device removed: " << device_address;
RemoveDevice(FidoBleDevice::GetId(device_address));
}
}
void FidoCableDiscovery::OnSetPowered() {
DCHECK(adapter());
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&FidoCableDiscovery::StartAdvertisement,
weak_factory_.GetWeakPtr()));
}
void FidoCableDiscovery::StartAdvertisement() {
DCHECK(adapter());
for (const auto& data : discovery_data_) {
adapter()->RegisterAdvertisement(
ConstructAdvertisementData(data.version, data.client_eid),
base::AdaptCallbackForRepeating(
base::BindOnce(&FidoCableDiscovery::OnAdvertisementRegistered,
weak_factory_.GetWeakPtr(), data.client_eid)),
base::AdaptCallbackForRepeating(
base::BindOnce(&FidoCableDiscovery::OnAdvertisementRegisterError,
weak_factory_.GetWeakPtr())));
}
}
void FidoCableDiscovery::OnAdvertisementRegistered(
const EidArray& client_eid,
scoped_refptr<BluetoothAdvertisement> advertisement) {
DVLOG(2) << "Advertisement registered.";
advertisements_.emplace(client_eid, std::move(advertisement));
RecordAdvertisementResult(true /* is_success */);
}
void FidoCableDiscovery::OnAdvertisementRegisterError(
BluetoothAdvertisement::ErrorCode error_code) {
DLOG(ERROR) << "Failed to register advertisement: " << error_code;
RecordAdvertisementResult(false /* is_success */);
}
void FidoCableDiscovery::RecordAdvertisementResult(bool is_success) {
is_success ? ++advertisement_success_counter_
: ++advertisement_failure_counter_;
// Wait until all advertisements are sent out.
if (advertisement_success_counter_ + advertisement_failure_counter_ !=
discovery_data_.size()) {
return;
}
// No advertisements succeeded, no point in starting scanning.
if (!advertisement_success_counter_) {
NotifyDiscoveryStarted(false);
return;
}
// At least one advertisement succeeded and all advertisement has been
// processed. Start scanning.
adapter()->StartDiscoverySessionWithFilter(
std::make_unique<BluetoothDiscoveryFilter>(
BluetoothTransport::BLUETOOTH_TRANSPORT_LE),
base::AdaptCallbackForRepeating(
base::BindOnce(&FidoCableDiscovery::OnStartDiscoverySessionWithFilter,
weak_factory_.GetWeakPtr())),
base::AdaptCallbackForRepeating(
base::BindOnce(&FidoCableDiscovery::OnStartDiscoverySessionError,
weak_factory_.GetWeakPtr())));
}
void FidoCableDiscovery::CableDeviceFound(BluetoothAdapter* adapter,
BluetoothDevice* device) {
const auto* found_cable_device_data = GetFoundCableDiscoveryData(device);
if (!found_cable_device_data)
return;
DVLOG(2) << "Found new Cable device.";
// Nonce is embedded as first 8 bytes of client EID.
std::array<uint8_t, 8> nonce;
bool extract_success = fido_parsing_utils::ExtractArray(
found_cable_device_data->client_eid, 0, &nonce);
if (!extract_success)
return;
auto cable_device = std::make_unique<FidoCableDevice>(device->GetAddress());
// At most one handshake messages should be exchanged for each Cable device.
if (!base::ContainsKey(cable_handshake_handlers_, cable_device->GetId())) {
ConductEncryptionHandshake(std::move(cable_device),
found_cable_device_data->session_pre_key, nonce);
}
}
void FidoCableDiscovery::ConductEncryptionHandshake(
std::unique_ptr<FidoCableDevice> cable_device,
base::span<const uint8_t, kSessionPreKeySize> session_pre_key,
base::span<const uint8_t, 8> nonce) {
auto handshake_handler =
CreateHandshakeHandler(cable_device.get(), session_pre_key, nonce);
auto* const handshake_handler_ptr = handshake_handler.get();
cable_handshake_handlers_.emplace(cable_device->GetId(),
std::move(handshake_handler));
handshake_handler_ptr->InitiateCableHandshake(
base::BindOnce(&FidoCableDiscovery::ValidateAuthenticatorHandshakeMessage,
weak_factory_.GetWeakPtr(), std::move(cable_device),
handshake_handler_ptr));
}
void FidoCableDiscovery::ValidateAuthenticatorHandshakeMessage(
std::unique_ptr<FidoCableDevice> cable_device,
FidoCableHandshakeHandler* handshake_handler,
base::Optional<std::vector<uint8_t>> handshake_response) {
if (!handshake_response)
return;
if (!handshake_handler->ValidateAuthenticatorHandshakeMessage(
*handshake_response))
return;
AddDevice(std::move(cable_device));
}
const FidoCableDiscovery::CableDiscoveryData*
FidoCableDiscovery::GetFoundCableDiscoveryData(
const BluetoothDevice* device) const {
const auto* service_data =
device->GetServiceDataForUUID(CableAdvertisementUUID());
DCHECK(service_data);
// Received service data from authenticator must have a flag that signals that
// the service data includes Cable EID.
if (service_data->empty() || !(service_data->at(0) >> 5 & 1u))
return nullptr;
EidArray received_authenticator_eid;
bool extract_success = fido_parsing_utils::ExtractArray(
*service_data, 2, &received_authenticator_eid);
if (!extract_success)
return nullptr;
auto discovery_data_iterator = std::find_if(
discovery_data_.begin(), discovery_data_.end(),
[&received_authenticator_eid](const auto& data) {
return received_authenticator_eid == data.authenticator_eid;
});
return discovery_data_iterator != discovery_data_.end()
? &(*discovery_data_iterator)
: nullptr;
}
} // namespace device