blob: 235d8985c2d5e6f8c6c2ea4e7069fe5489d84b91 [file] [log] [blame]
// Copyright 2017 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/ble/fido_ble_connection.h"
#include <algorithm>
#include <ostream>
#include <utility>
#include "base/barrier_closure.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "components/device_event_log/device_event_log.h"
#include "device/bluetooth/bluetooth_gatt_connection.h"
#include "device/bluetooth/bluetooth_gatt_notify_session.h"
#include "device/bluetooth/bluetooth_remote_gatt_characteristic.h"
#include "device/bluetooth/bluetooth_remote_gatt_service.h"
#include "device/bluetooth/public/cpp/bluetooth_uuid.h"
#include "device/fido/ble/fido_ble_uuids.h"
namespace device {
namespace {
using ServiceRevisionsCallback =
base::OnceCallback<void(std::vector<FidoBleConnection::ServiceRevision>)>;
constexpr const char* ToString(BluetoothDevice::ConnectErrorCode error_code) {
switch (error_code) {
case BluetoothDevice::ERROR_AUTH_CANCELED:
return "ERROR_AUTH_CANCELED";
case BluetoothDevice::ERROR_AUTH_FAILED:
return "ERROR_AUTH_FAILED";
case BluetoothDevice::ERROR_AUTH_REJECTED:
return "ERROR_AUTH_REJECTED";
case BluetoothDevice::ERROR_AUTH_TIMEOUT:
return "ERROR_AUTH_TIMEOUT";
case BluetoothDevice::ERROR_FAILED:
return "ERROR_FAILED";
case BluetoothDevice::ERROR_INPROGRESS:
return "ERROR_INPROGRESS";
case BluetoothDevice::ERROR_UNKNOWN:
return "ERROR_UNKNOWN";
case BluetoothDevice::ERROR_UNSUPPORTED_DEVICE:
return "ERROR_UNSUPPORTED_DEVICE";
default:
NOTREACHED();
return "";
}
}
constexpr const char* ToString(BluetoothGattService::GattErrorCode error_code) {
switch (error_code) {
case BluetoothGattService::GATT_ERROR_UNKNOWN:
return "GATT_ERROR_UNKNOWN";
case BluetoothGattService::GATT_ERROR_FAILED:
return "GATT_ERROR_FAILED";
case BluetoothGattService::GATT_ERROR_IN_PROGRESS:
return "GATT_ERROR_IN_PROGRESS";
case BluetoothGattService::GATT_ERROR_INVALID_LENGTH:
return "GATT_ERROR_INVALID_LENGTH";
case BluetoothGattService::GATT_ERROR_NOT_PERMITTED:
return "GATT_ERROR_NOT_PERMITTED";
case BluetoothGattService::GATT_ERROR_NOT_AUTHORIZED:
return "GATT_ERROR_NOT_AUTHORIZED";
case BluetoothGattService::GATT_ERROR_NOT_PAIRED:
return "GATT_ERROR_NOT_PAIRED";
case BluetoothGattService::GATT_ERROR_NOT_SUPPORTED:
return "GATT_ERROR_NOT_SUPPORTED";
default:
NOTREACHED();
return "";
}
}
std::ostream& operator<<(std::ostream& os,
FidoBleConnection::ServiceRevision revision) {
switch (revision) {
case FidoBleConnection::ServiceRevision::kU2f11:
return os << "U2F 1.1";
case FidoBleConnection::ServiceRevision::kU2f12:
return os << "U2F 1.2";
case FidoBleConnection::ServiceRevision::kFido2:
return os << "FIDO2";
}
NOTREACHED();
return os;
}
void OnWriteRemoteCharacteristic(FidoBleConnection::WriteCallback callback) {
FIDO_LOG(DEBUG) << "Writing Remote Characteristic Succeeded.";
std::move(callback).Run(true);
}
void OnWriteRemoteCharacteristicError(
FidoBleConnection::WriteCallback callback,
BluetoothGattService::GattErrorCode error_code) {
FIDO_LOG(ERROR) << "Writing Remote Characteristic Failed: "
<< ToString(error_code);
std::move(callback).Run(false);
}
void OnReadServiceRevisionBitfield(ServiceRevisionsCallback callback,
const std::vector<uint8_t>& value) {
if (value.empty()) {
FIDO_LOG(DEBUG) << "Service Revision Bitfield is empty.";
std::move(callback).Run({});
return;
}
if (value.size() != 1u) {
FIDO_LOG(DEBUG) << "Service Revision Bitfield has unexpected size: "
<< value.size() << ". Ignoring all but the first byte.";
}
const uint8_t bitset = value[0];
if (bitset & 0x1F) {
FIDO_LOG(DEBUG) << "Service Revision Bitfield has unexpected bits set: "
<< base::StringPrintf("0x%02X", bitset)
<< ". Ignoring all but the first three bits.";
}
std::vector<FidoBleConnection::ServiceRevision> service_revisions;
for (auto revision : {FidoBleConnection::ServiceRevision::kU2f11,
FidoBleConnection::ServiceRevision::kU2f12,
FidoBleConnection::ServiceRevision::kFido2}) {
if (bitset & static_cast<uint8_t>(revision)) {
FIDO_LOG(DEBUG) << "Detected Support for " << revision << ".";
service_revisions.push_back(revision);
}
}
std::move(callback).Run(std::move(service_revisions));
}
void OnReadServiceRevisionBitfieldError(
ServiceRevisionsCallback callback,
BluetoothGattService::GattErrorCode error_code) {
FIDO_LOG(ERROR) << "Error while reading Service Revision Bitfield: "
<< ToString(error_code);
std::move(callback).Run({});
}
} // namespace
FidoBleConnection::FidoBleConnection(BluetoothAdapter* adapter,
std::string device_address,
ReadCallback read_callback)
: adapter_(adapter),
address_(std::move(device_address)),
read_callback_(std::move(read_callback)) {
DCHECK(adapter_);
adapter_->AddObserver(this);
DCHECK(!address_.empty());
}
FidoBleConnection::~FidoBleConnection() {
adapter_->RemoveObserver(this);
}
BluetoothDevice* FidoBleConnection::GetBleDevice() {
return adapter_->GetDevice(address());
}
const BluetoothDevice* FidoBleConnection::GetBleDevice() const {
return adapter_->GetDevice(address());
}
FidoBleConnection::FidoBleConnection(BluetoothAdapter* adapter,
std::string device_address)
: adapter_(adapter), address_(std::move(device_address)) {
adapter_->AddObserver(this);
}
void FidoBleConnection::Connect(ConnectionCallback callback) {
auto* device = GetBleDevice();
if (!device) {
FIDO_LOG(ERROR) << "Failed to get Device.";
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), false));
return;
}
pending_connection_callback_ = std::move(callback);
FIDO_LOG(DEBUG) << "Creating a GATT connection...";
device->CreateGattConnection(
base::Bind(&FidoBleConnection::OnCreateGattConnection,
weak_factory_.GetWeakPtr()),
base::Bind(&FidoBleConnection::OnCreateGattConnectionError,
weak_factory_.GetWeakPtr()));
}
void FidoBleConnection::ReadControlPointLength(
ControlPointLengthCallback callback) {
const auto* fido_service = GetFidoService();
if (!fido_service) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), base::nullopt));
return;
}
if (!control_point_length_id_) {
FIDO_LOG(ERROR) << "Failed to get Control Point Length.";
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), base::nullopt));
return;
}
BluetoothRemoteGattCharacteristic* control_point_length =
fido_service->GetCharacteristic(*control_point_length_id_);
if (!control_point_length) {
FIDO_LOG(ERROR) << "No Control Point Length characteristic present.";
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), base::nullopt));
return;
}
FIDO_LOG(DEBUG) << "Read Control Point Length";
// Work around legacy APIs. Only one of the callbacks to
// ReadRemoteCharacteristic() gets invoked, but we don't know which one.
auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback));
control_point_length->ReadRemoteCharacteristic(
base::Bind(OnReadControlPointLength, copyable_callback),
base::Bind(OnReadControlPointLengthError, copyable_callback));
}
void FidoBleConnection::WriteControlPoint(const std::vector<uint8_t>& data,
WriteCallback callback) {
const auto* fido_service = GetFidoService();
if (!fido_service) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), false));
return;
}
if (!control_point_id_) {
FIDO_LOG(ERROR) << "Failed to get Control Point.";
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), false));
return;
}
BluetoothRemoteGattCharacteristic* control_point =
fido_service->GetCharacteristic(*control_point_id_);
if (!control_point) {
FIDO_LOG(ERROR) << "Control Point characteristic not present.";
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), false));
return;
}
#if defined(OS_MACOSX)
// Attempt a write without response for performance reasons. Fall back to a
// confirmed write in case of failure, e.g. when the characteristic does not
// provide the required property.
if (control_point->WriteWithoutResponse(data)) {
FIDO_LOG(DEBUG) << "Write without response succeeded.";
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), true));
return;
}
#endif // defined(OS_MACOSX)
FIDO_LOG(DEBUG) << "Wrote Control Point.";
auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback));
control_point->WriteRemoteCharacteristic(
data, base::Bind(OnWriteRemoteCharacteristic, copyable_callback),
base::Bind(OnWriteRemoteCharacteristicError, copyable_callback));
}
void FidoBleConnection::OnCreateGattConnection(
std::unique_ptr<BluetoothGattConnection> connection) {
DCHECK(pending_connection_callback_);
connection_ = std::move(connection);
BluetoothDevice* device = adapter_->GetDevice(address_);
if (!device) {
FIDO_LOG(ERROR) << "Failed to get Device.";
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(pending_connection_callback_), false));
return;
}
if (device->IsGattServicesDiscoveryComplete())
ConnectToFidoService();
else
waiting_for_gatt_discovery_ = true;
}
void FidoBleConnection::OnCreateGattConnectionError(
BluetoothDevice::ConnectErrorCode error_code) {
DCHECK(pending_connection_callback_);
FIDO_LOG(ERROR) << "CreateGattConnection() failed: " << ToString(error_code);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(pending_connection_callback_), false));
}
void FidoBleConnection::ConnectToFidoService() {
FIDO_LOG(EVENT) << "Attempting to connect to a Fido service.";
DCHECK(pending_connection_callback_);
const auto* fido_service = GetFidoService();
if (!fido_service) {
FIDO_LOG(ERROR) << "Failed to get Fido Service.";
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(pending_connection_callback_), false));
return;
}
for (const auto* characteristic : fido_service->GetCharacteristics()) {
std::string uuid = characteristic->GetUUID().canonical_value();
if (uuid == kFidoControlPointLengthUUID) {
control_point_length_id_ = characteristic->GetIdentifier();
FIDO_LOG(DEBUG) << "Got Fido Control Point Length: "
<< *control_point_length_id_;
continue;
}
if (uuid == kFidoControlPointUUID) {
control_point_id_ = characteristic->GetIdentifier();
FIDO_LOG(DEBUG) << "Got Fido Control Point: " << *control_point_id_;
continue;
}
if (uuid == kFidoStatusUUID) {
status_id_ = characteristic->GetIdentifier();
FIDO_LOG(DEBUG) << "Got Fido Status: " << *status_id_;
continue;
}
if (uuid == kFidoServiceRevisionUUID) {
service_revision_id_ = characteristic->GetIdentifier();
FIDO_LOG(DEBUG) << "Got Fido Service Revision: " << *service_revision_id_;
continue;
}
if (uuid == kFidoServiceRevisionBitfieldUUID) {
service_revision_bitfield_id_ = characteristic->GetIdentifier();
FIDO_LOG(DEBUG) << "Got Fido Service Revision Bitfield: "
<< *service_revision_bitfield_id_;
}
}
if (!control_point_length_id_ || !control_point_id_ || !status_id_ ||
(!service_revision_id_ && !service_revision_bitfield_id_)) {
FIDO_LOG(ERROR) << "Fido Characteristics missing.";
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(pending_connection_callback_), false));
return;
}
// In case the bitfield characteristic is present, the client has to select a
// supported version by writing the corresponding bit. Reference:
// https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#ble-protocol-overview
if (service_revision_bitfield_id_) {
auto callback = base::Bind(&FidoBleConnection::OnReadServiceRevisions,
weak_factory_.GetWeakPtr());
fido_service->GetCharacteristic(*service_revision_bitfield_id_)
->ReadRemoteCharacteristic(
base::Bind(OnReadServiceRevisionBitfield, callback),
base::Bind(OnReadServiceRevisionBitfieldError, callback));
return;
}
StartNotifySession();
}
void FidoBleConnection::OnReadServiceRevisions(
std::vector<ServiceRevision> service_revisions) {
DCHECK(pending_connection_callback_);
if (service_revisions.empty()) {
FIDO_LOG(ERROR) << "Could not obtain Service Revisions.";
std::move(pending_connection_callback_).Run(false);
return;
}
// Write the most recent supported service revision back to the
// characteristic. Note that this information is currently not used in another
// way, as we will still attempt a CTAP GetInfo() command, even if only U2F is
// supported.
// TODO(https://crbug.com/780078): Consider short circuiting to the
// U2F logic if FIDO2 is not supported.
DCHECK_EQ(
*std::min_element(service_revisions.begin(), service_revisions.end()),
service_revisions.back());
WriteServiceRevision(service_revisions.back());
}
void FidoBleConnection::WriteServiceRevision(ServiceRevision service_revision) {
auto callback = base::BindOnce(&FidoBleConnection::OnServiceRevisionWritten,
weak_factory_.GetWeakPtr());
const auto* fido_service = GetFidoService();
if (!fido_service) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), false));
return;
}
auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback));
DCHECK(service_revision_bitfield_id_);
fido_service->GetCharacteristic(*service_revision_bitfield_id_)
->WriteRemoteCharacteristic(
{static_cast<uint8_t>(service_revision)},
base::Bind(OnWriteRemoteCharacteristic, copyable_callback),
base::Bind(OnWriteRemoteCharacteristicError, copyable_callback));
}
void FidoBleConnection::OnServiceRevisionWritten(bool success) {
DCHECK(pending_connection_callback_);
if (success) {
FIDO_LOG(DEBUG) << "Service Revision successfully written.";
StartNotifySession();
return;
}
FIDO_LOG(ERROR) << "Failed to write Service Revision.";
std::move(pending_connection_callback_).Run(false);
}
void FidoBleConnection::StartNotifySession() {
DCHECK(pending_connection_callback_);
const auto* fido_service = GetFidoService();
if (!fido_service) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(pending_connection_callback_), false));
return;
}
DCHECK(status_id_);
fido_service->GetCharacteristic(*status_id_)
->StartNotifySession(
base::Bind(&FidoBleConnection::OnStartNotifySession,
weak_factory_.GetWeakPtr()),
base::Bind(&FidoBleConnection::OnStartNotifySessionError,
weak_factory_.GetWeakPtr()));
}
void FidoBleConnection::OnStartNotifySession(
std::unique_ptr<BluetoothGattNotifySession> notify_session) {
notify_session_ = std::move(notify_session);
FIDO_LOG(DEBUG) << "Created notification session. Connection established.";
std::move(pending_connection_callback_).Run(true);
}
void FidoBleConnection::OnStartNotifySessionError(
BluetoothGattService::GattErrorCode error_code) {
FIDO_LOG(ERROR) << "StartNotifySession() failed: " << ToString(error_code);
std::move(pending_connection_callback_).Run(false);
}
void FidoBleConnection::DeviceAddressChanged(BluetoothAdapter* adapter,
BluetoothDevice* device,
const std::string& old_address) {
if (address_ == old_address)
address_ = device->GetAddress();
}
void FidoBleConnection::GattCharacteristicValueChanged(
BluetoothAdapter* adapter,
BluetoothRemoteGattCharacteristic* characteristic,
const std::vector<uint8_t>& value) {
if (characteristic->GetIdentifier() != status_id_)
return;
FIDO_LOG(DEBUG) << "Status characteristic value changed.";
read_callback_.Run(value);
}
void FidoBleConnection::GattServicesDiscovered(BluetoothAdapter* adapter,
BluetoothDevice* device) {
if (adapter != adapter_ || device->GetAddress() != address_)
return;
if (waiting_for_gatt_discovery_) {
waiting_for_gatt_discovery_ = false;
ConnectToFidoService();
}
}
const BluetoothRemoteGattService* FidoBleConnection::GetFidoService() {
if (!connection_ || !connection_->IsConnected()) {
FIDO_LOG(ERROR) << "No BLE connection.";
return nullptr;
}
DCHECK_EQ(address_, connection_->GetDeviceAddress());
BluetoothDevice* device = GetBleDevice();
for (const auto* service : device->GetGattServices()) {
// This assumes that no device is representing as both a FIDO BLE
// and a caBLE device.
if (service->GetUUID() == BluetoothUUID(kFidoServiceUUID) ||
service->GetUUID() == BluetoothUUID(kCableAdvertisementUUID128)) {
FIDO_LOG(EVENT) << "Found caBLE service UUID: "
<< service->GetUUID().value();
return service;
}
}
FIDO_LOG(ERROR) << "No Fido service present.";
return nullptr;
}
// static
void FidoBleConnection::OnReadControlPointLength(
ControlPointLengthCallback callback,
const std::vector<uint8_t>& value) {
if (value.size() != 2) {
FIDO_LOG(ERROR) << "Wrong Control Point Length: " << value.size()
<< " bytes";
std::move(callback).Run(base::nullopt);
return;
}
uint16_t length = (value[0] << 8) | value[1];
FIDO_LOG(DEBUG) << "Control Point Length: " << length;
std::move(callback).Run(length);
}
// static
void FidoBleConnection::OnReadControlPointLengthError(
ControlPointLengthCallback callback,
BluetoothGattService::GattErrorCode error_code) {
FIDO_LOG(ERROR) << "Error reading Control Point Length: "
<< ToString(error_code);
std::move(callback).Run(base::nullopt);
}
} // namespace device