| // 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/u2f_ble_connection.h" |
| |
| #include <utility> |
| |
| #include "base/barrier_closure.h" |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/logging.h" |
| #include "device/bluetooth/bluetooth_adapter_factory.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/bluetooth_uuid.h" |
| #include "device/fido/u2f_ble_uuids.h" |
| |
| namespace device { |
| |
| namespace { |
| |
| 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 ""; |
| } |
| } |
| |
| } // namespace |
| |
| U2fBleConnection::U2fBleConnection(std::string device_address) |
| : address_(std::move(device_address)), weak_factory_(this) {} |
| |
| U2fBleConnection::U2fBleConnection( |
| std::string device_address, |
| ConnectionStatusCallback connection_status_callback, |
| ReadCallback read_callback) |
| : address_(std::move(device_address)), |
| connection_status_callback_(std::move(connection_status_callback)), |
| read_callback_(std::move(read_callback)), |
| weak_factory_(this) { |
| DCHECK(!address_.empty()); |
| } |
| |
| U2fBleConnection::~U2fBleConnection() { |
| if (adapter_) |
| adapter_->RemoveObserver(this); |
| } |
| |
| void U2fBleConnection::Connect() { |
| BluetoothAdapterFactory::GetAdapter( |
| base::Bind(&U2fBleConnection::OnGetAdapter, weak_factory_.GetWeakPtr())); |
| } |
| |
| void U2fBleConnection::ReadControlPointLength( |
| ControlPointLengthCallback callback) { |
| const BluetoothRemoteGattService* u2f_service = GetU2fService(); |
| if (!u2f_service) { |
| std::move(callback).Run(base::nullopt); |
| return; |
| } |
| |
| BluetoothRemoteGattCharacteristic* control_point_length = |
| u2f_service->GetCharacteristic(*control_point_length_id_); |
| if (!control_point_length) { |
| DLOG(ERROR) << "No Control Point Length characteristic present."; |
| std::move(callback).Run(base::nullopt); |
| return; |
| } |
| |
| auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback)); |
| control_point_length->ReadRemoteCharacteristic( |
| base::Bind(OnReadControlPointLength, copyable_callback), |
| base::Bind(OnReadControlPointLengthError, copyable_callback)); |
| } |
| |
| void U2fBleConnection::ReadServiceRevisions(ServiceRevisionsCallback callback) { |
| const BluetoothRemoteGattService* u2f_service = GetU2fService(); |
| if (!u2f_service) { |
| std::move(callback).Run({}); |
| return; |
| } |
| |
| DCHECK(service_revision_id_ || service_revision_bitfield_id_); |
| BluetoothRemoteGattCharacteristic* service_revision = |
| service_revision_id_ |
| ? u2f_service->GetCharacteristic(*service_revision_id_) |
| : nullptr; |
| |
| BluetoothRemoteGattCharacteristic* service_revision_bitfield = |
| service_revision_bitfield_id_ |
| ? u2f_service->GetCharacteristic(*service_revision_bitfield_id_) |
| : nullptr; |
| |
| if (!service_revision && !service_revision_bitfield) { |
| DLOG(ERROR) << "Service Revision Characteristics do not exist."; |
| std::move(callback).Run({}); |
| return; |
| } |
| |
| // Start from a clean state. |
| service_revisions_.clear(); |
| |
| // In order to obtain the full set of supported service revisions it is |
| // possible that both the |service_revision_| and |service_revision_bitfield_| |
| // characteristics must be read. Potentially we need to take the union of |
| // the individually supported service revisions, hence the indirection to |
| // ReturnServiceRevisions() is introduced. |
| base::Closure copyable_callback = base::AdaptCallbackForRepeating( |
| base::BindOnce(&U2fBleConnection::ReturnServiceRevisions, |
| weak_factory_.GetWeakPtr(), std::move(callback))); |
| |
| // If the Service Revision Bitfield characteristic is not present, only |
| // attempt to read the Service Revision characteristic. |
| if (!service_revision_bitfield) { |
| service_revision->ReadRemoteCharacteristic( |
| base::Bind(&U2fBleConnection::OnReadServiceRevision, |
| weak_factory_.GetWeakPtr(), copyable_callback), |
| base::Bind(&U2fBleConnection::OnReadServiceRevisionError, |
| weak_factory_.GetWeakPtr(), copyable_callback)); |
| return; |
| } |
| |
| // If the Service Revision characteristic is not present, only |
| // attempt to read the Service Revision Bitfield characteristic. |
| if (!service_revision) { |
| service_revision_bitfield->ReadRemoteCharacteristic( |
| base::Bind(&U2fBleConnection::OnReadServiceRevisionBitfield, |
| weak_factory_.GetWeakPtr(), copyable_callback), |
| base::Bind(&U2fBleConnection::OnReadServiceRevisionBitfieldError, |
| weak_factory_.GetWeakPtr(), copyable_callback)); |
| return; |
| } |
| |
| // This is the case where both characteristics are present. These reads can |
| // happen in parallel, but both must finish before a result can be returned. |
| // Hence a BarrierClosure is introduced invoking ReturnServiceRevisions() once |
| // both characteristic reads are done. |
| base::RepeatingClosure barrier_closure = |
| base::BarrierClosure(2, copyable_callback); |
| |
| service_revision->ReadRemoteCharacteristic( |
| base::Bind(&U2fBleConnection::OnReadServiceRevision, |
| weak_factory_.GetWeakPtr(), barrier_closure), |
| base::Bind(&U2fBleConnection::OnReadServiceRevisionError, |
| weak_factory_.GetWeakPtr(), barrier_closure)); |
| |
| service_revision_bitfield->ReadRemoteCharacteristic( |
| base::Bind(&U2fBleConnection::OnReadServiceRevisionBitfield, |
| weak_factory_.GetWeakPtr(), barrier_closure), |
| base::Bind(&U2fBleConnection::OnReadServiceRevisionBitfieldError, |
| weak_factory_.GetWeakPtr(), barrier_closure)); |
| } |
| |
| void U2fBleConnection::WriteControlPoint(const std::vector<uint8_t>& data, |
| WriteCallback callback) { |
| const BluetoothRemoteGattService* u2f_service = GetU2fService(); |
| if (!u2f_service) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| BluetoothRemoteGattCharacteristic* control_point = |
| u2f_service->GetCharacteristic(*control_point_id_); |
| if (!control_point) { |
| DLOG(ERROR) << "Control Point characteristic not present."; |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback)); |
| control_point->WriteRemoteCharacteristic( |
| data, base::Bind(OnWrite, copyable_callback), |
| base::Bind(OnWriteError, copyable_callback)); |
| } |
| |
| void U2fBleConnection::WriteServiceRevision(ServiceRevision service_revision, |
| WriteCallback callback) { |
| const BluetoothRemoteGattService* u2f_service = GetU2fService(); |
| if (!u2f_service) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| BluetoothRemoteGattCharacteristic* service_revision_bitfield = |
| u2f_service->GetCharacteristic(*service_revision_bitfield_id_); |
| if (!service_revision_bitfield) { |
| DLOG(ERROR) << "Service Revision Bitfield characteristic not present."; |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| std::vector<uint8_t> payload; |
| switch (service_revision) { |
| case ServiceRevision::VERSION_1_1: |
| payload.push_back(0x80); |
| break; |
| case ServiceRevision::VERSION_1_2: |
| payload.push_back(0x40); |
| break; |
| default: |
| DLOG(ERROR) |
| << "Write Service Revision Failed: Unsupported Service Revision."; |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| auto copyable_callback = base::AdaptCallbackForRepeating(std::move(callback)); |
| service_revision_bitfield->WriteRemoteCharacteristic( |
| payload, base::Bind(OnWrite, copyable_callback), |
| base::Bind(OnWriteError, copyable_callback)); |
| } |
| |
| void U2fBleConnection::OnGetAdapter(scoped_refptr<BluetoothAdapter> adapter) { |
| if (!adapter) { |
| DLOG(ERROR) << "Failed to get Adapter."; |
| OnConnectionError(); |
| return; |
| } |
| |
| DVLOG(2) << "Got Adapter: " << adapter->GetAddress(); |
| adapter_ = std::move(adapter); |
| adapter_->AddObserver(this); |
| CreateGattConnection(); |
| } |
| |
| void U2fBleConnection::CreateGattConnection() { |
| BluetoothDevice* device = adapter_->GetDevice(address_); |
| if (!device) { |
| DLOG(ERROR) << "Failed to get Device."; |
| OnConnectionError(); |
| return; |
| } |
| |
| device->CreateGattConnection( |
| base::Bind(&U2fBleConnection::OnCreateGattConnection, |
| weak_factory_.GetWeakPtr()), |
| base::Bind(&U2fBleConnection::OnCreateGattConnectionError, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void U2fBleConnection::OnCreateGattConnection( |
| std::unique_ptr<BluetoothGattConnection> connection) { |
| connection_ = std::move(connection); |
| |
| BluetoothDevice* device = adapter_->GetDevice(address_); |
| if (!device) { |
| DLOG(ERROR) << "Failed to get Device."; |
| OnConnectionError(); |
| return; |
| } |
| |
| if (device->IsGattServicesDiscoveryComplete()) |
| ConnectToU2fService(); |
| } |
| |
| void U2fBleConnection::OnCreateGattConnectionError( |
| BluetoothDevice::ConnectErrorCode error_code) { |
| DLOG(ERROR) << "CreateGattConnection() failed: " << ToString(error_code); |
| OnConnectionError(); |
| } |
| |
| void U2fBleConnection::ConnectToU2fService() { |
| BluetoothDevice* device = adapter_->GetDevice(address_); |
| if (!device) { |
| DLOG(ERROR) << "Failed to get Device."; |
| OnConnectionError(); |
| return; |
| } |
| |
| DCHECK(device->IsGattServicesDiscoveryComplete()); |
| const std::vector<BluetoothRemoteGattService*> services = |
| device->GetGattServices(); |
| auto found = |
| std::find_if(services.begin(), services.end(), [](const auto* service) { |
| return service->GetUUID().canonical_value() == kU2fServiceUUID; |
| }); |
| |
| if (found == services.end()) { |
| DLOG(ERROR) << "Failed to get U2F Service."; |
| OnConnectionError(); |
| return; |
| } |
| |
| const BluetoothRemoteGattService* u2f_service = *found; |
| u2f_service_id_ = u2f_service->GetIdentifier(); |
| DVLOG(2) << "Got U2F Service: " << *u2f_service_id_; |
| |
| for (const auto* characteristic : u2f_service->GetCharacteristics()) { |
| // NOTE: Since GetUUID() returns a temporary |uuid| can't be a reference, |
| // even though canonical_value() returns a const reference. |
| const std::string uuid = characteristic->GetUUID().canonical_value(); |
| if (uuid == kU2fControlPointLengthUUID) { |
| control_point_length_id_ = characteristic->GetIdentifier(); |
| DVLOG(2) << "Got U2F Control Point Length: " << *control_point_length_id_; |
| } else if (uuid == kU2fControlPointUUID) { |
| control_point_id_ = characteristic->GetIdentifier(); |
| DVLOG(2) << "Got U2F Control Point: " << *control_point_id_; |
| } else if (uuid == kU2fStatusUUID) { |
| status_id_ = characteristic->GetIdentifier(); |
| DVLOG(2) << "Got U2F Status: " << *status_id_; |
| } else if (uuid == kU2fServiceRevisionUUID) { |
| service_revision_id_ = characteristic->GetIdentifier(); |
| DVLOG(2) << "Got U2F Service Revision: " << *service_revision_id_; |
| } else if (uuid == kU2fServiceRevisionBitfieldUUID) { |
| service_revision_bitfield_id_ = characteristic->GetIdentifier(); |
| DVLOG(2) << "Got U2F Service Revision Bitfield: " |
| << *service_revision_bitfield_id_; |
| } |
| } |
| |
| if (!control_point_length_id_ || !control_point_id_ || !status_id_ || |
| (!service_revision_id_ && !service_revision_bitfield_id_)) { |
| DLOG(ERROR) << "U2F characteristics missing."; |
| OnConnectionError(); |
| return; |
| } |
| |
| u2f_service->GetCharacteristic(*status_id_) |
| ->StartNotifySession( |
| base::Bind(&U2fBleConnection::OnStartNotifySession, |
| weak_factory_.GetWeakPtr()), |
| base::Bind(&U2fBleConnection::OnStartNotifySessionError, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void U2fBleConnection::OnStartNotifySession( |
| std::unique_ptr<BluetoothGattNotifySession> notify_session) { |
| notify_session_ = std::move(notify_session); |
| DVLOG(2) << "Created notification session. Connection established."; |
| connection_status_callback_.Run(true); |
| } |
| |
| void U2fBleConnection::OnStartNotifySessionError( |
| BluetoothGattService::GattErrorCode error_code) { |
| DLOG(ERROR) << "StartNotifySession() failed: " << ToString(error_code); |
| OnConnectionError(); |
| } |
| |
| void U2fBleConnection::OnConnectionError() { |
| connection_status_callback_.Run(false); |
| |
| connection_.reset(); |
| notify_session_.reset(); |
| |
| u2f_service_id_.reset(); |
| control_point_length_id_.reset(); |
| control_point_id_.reset(); |
| status_id_.reset(); |
| service_revision_id_.reset(); |
| service_revision_bitfield_id_.reset(); |
| } |
| |
| const BluetoothRemoteGattService* U2fBleConnection::GetU2fService() const { |
| if (!adapter_) { |
| DLOG(ERROR) << "No adapter present."; |
| return nullptr; |
| } |
| |
| const BluetoothDevice* device = adapter_->GetDevice(address_); |
| if (!device) { |
| DLOG(ERROR) << "No device present."; |
| return nullptr; |
| } |
| |
| if (!u2f_service_id_) { |
| DLOG(ERROR) << "Unknown U2F service id."; |
| return nullptr; |
| } |
| |
| const BluetoothRemoteGattService* u2f_service = |
| device->GetGattService(*u2f_service_id_); |
| if (!u2f_service) { |
| DLOG(ERROR) << "No U2F service present."; |
| return nullptr; |
| } |
| |
| return u2f_service; |
| } |
| |
| void U2fBleConnection::DeviceAdded(BluetoothAdapter* adapter, |
| BluetoothDevice* device) { |
| if (adapter != adapter_ || device->GetAddress() != address_) |
| return; |
| CreateGattConnection(); |
| } |
| |
| void U2fBleConnection::DeviceAddressChanged(BluetoothAdapter* adapter, |
| BluetoothDevice* device, |
| const std::string& old_address) { |
| if (adapter != adapter_ || old_address != address_) |
| return; |
| address_ = device->GetAddress(); |
| } |
| |
| void U2fBleConnection::DeviceChanged(BluetoothAdapter* adapter, |
| BluetoothDevice* device) { |
| if (adapter != adapter_ || device->GetAddress() != address_) |
| return; |
| if (connection_ && !device->IsGattConnected()) { |
| DLOG(ERROR) << "GATT Disconnected: " << device->GetAddress(); |
| OnConnectionError(); |
| } |
| } |
| |
| void U2fBleConnection::GattCharacteristicValueChanged( |
| BluetoothAdapter* adapter, |
| BluetoothRemoteGattCharacteristic* characteristic, |
| const std::vector<uint8_t>& value) { |
| if (characteristic->GetIdentifier() != status_id_) |
| return; |
| DVLOG(2) << "Status characteristic value changed."; |
| read_callback_.Run(value); |
| } |
| |
| void U2fBleConnection::GattServicesDiscovered(BluetoothAdapter* adapter, |
| BluetoothDevice* device) { |
| if (adapter != adapter_ || device->GetAddress() != address_) |
| return; |
| ConnectToU2fService(); |
| } |
| |
| // static |
| void U2fBleConnection::OnReadControlPointLength( |
| ControlPointLengthCallback callback, |
| const std::vector<uint8_t>& value) { |
| if (value.size() != 2) { |
| DLOG(ERROR) << "Wrong Control Point Length: " << value.size() << " bytes"; |
| std::move(callback).Run(base::nullopt); |
| return; |
| } |
| |
| uint16_t length = (value[0] << 8) | value[1]; |
| DVLOG(2) << "Control Point Length: " << length; |
| std::move(callback).Run(length); |
| } |
| |
| // static |
| void U2fBleConnection::OnReadControlPointLengthError( |
| ControlPointLengthCallback callback, |
| BluetoothGattService::GattErrorCode error_code) { |
| DLOG(ERROR) << "Error reading Control Point Length: " << ToString(error_code); |
| std::move(callback).Run(base::nullopt); |
| } |
| |
| void U2fBleConnection::OnReadServiceRevision( |
| base::OnceClosure callback, |
| const std::vector<uint8_t>& value) { |
| std::string service_revision(value.begin(), value.end()); |
| DVLOG(2) << "Service Revision: " << service_revision; |
| |
| if (service_revision == "1.0") { |
| service_revisions_.insert(ServiceRevision::VERSION_1_0); |
| } else if (service_revision == "1.1") { |
| service_revisions_.insert(ServiceRevision::VERSION_1_1); |
| } else if (service_revision == "1.2") { |
| service_revisions_.insert(ServiceRevision::VERSION_1_2); |
| } else { |
| DLOG(ERROR) << "Unknown Service Revision: " << service_revision; |
| std::move(callback).Run(); |
| return; |
| } |
| |
| std::move(callback).Run(); |
| } |
| |
| void U2fBleConnection::OnReadServiceRevisionError( |
| base::OnceClosure callback, |
| BluetoothGattService::GattErrorCode error_code) { |
| DLOG(ERROR) << "Error reading Service Revision: " << ToString(error_code); |
| std::move(callback).Run(); |
| } |
| |
| void U2fBleConnection::OnReadServiceRevisionBitfield( |
| base::OnceClosure callback, |
| const std::vector<uint8_t>& value) { |
| if (value.empty()) { |
| DLOG(ERROR) << "Service Revision Bitfield is empty."; |
| std::move(callback).Run(); |
| return; |
| } |
| |
| if (value.size() != 1u) { |
| DLOG(ERROR) << "Service Revision Bitfield has unexpected size: " |
| << value.size() << ". Ignoring all but the first byte."; |
| } |
| |
| const uint8_t bitset = value[0]; |
| if (bitset & 0x3F) { |
| DLOG(ERROR) << "Service Revision Bitfield has unexpected bits set: 0x" |
| << std::hex << (bitset & 0x3F) |
| << ". Ignoring all but the first two bits."; |
| } |
| |
| if (bitset & 0x80) { |
| service_revisions_.insert(ServiceRevision::VERSION_1_1); |
| DVLOG(2) << "Detected Support for Service Revision 1.1"; |
| } |
| |
| if (bitset & 0x40) { |
| service_revisions_.insert(ServiceRevision::VERSION_1_2); |
| DVLOG(2) << "Detected Support for Service Revision 1.2"; |
| } |
| |
| std::move(callback).Run(); |
| } |
| |
| void U2fBleConnection::OnReadServiceRevisionBitfieldError( |
| base::OnceClosure callback, |
| BluetoothGattService::GattErrorCode error_code) { |
| DLOG(ERROR) << "Error reading Service Revision Bitfield: " |
| << ToString(error_code); |
| std::move(callback).Run(); |
| } |
| |
| void U2fBleConnection::ReturnServiceRevisions( |
| ServiceRevisionsCallback callback) { |
| std::move(callback).Run(std::move(service_revisions_)); |
| } |
| |
| // static |
| void U2fBleConnection::OnWrite(WriteCallback callback) { |
| DVLOG(2) << "Write succeeded."; |
| std::move(callback).Run(true); |
| } |
| |
| // static |
| void U2fBleConnection::OnWriteError( |
| WriteCallback callback, |
| BluetoothGattService::GattErrorCode error_code) { |
| DLOG(ERROR) << "Write Failed: " << ToString(error_code); |
| std::move(callback).Run(false); |
| } |
| |
| } // namespace device |