1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "device/fido/ble/fido_ble_device.h"
6 
7 #include <bitset>
8 
9 #include "base/bind.h"
10 #include "base/bind_helpers.h"
11 #include "base/strings/string_number_conversions.h"
12 #include "base/strings/string_piece.h"
13 #include "components/apdu/apdu_response.h"
14 #include "components/device_event_log/device_event_log.h"
15 #include "device/bluetooth/public/cpp/bluetooth_uuid.h"
16 #include "device/fido/ble/fido_ble_frames.h"
17 #include "device/fido/ble/fido_ble_uuids.h"
18 #include "device/fido/fido_constants.h"
19 
20 namespace device {
21 
FidoBleDevice(BluetoothAdapter * adapter,std::string address,Type type)22 FidoBleDevice::FidoBleDevice(BluetoothAdapter* adapter,
23                              std::string address,
24                              Type type) {
25   const BluetoothUUID service_uuid(
26       type == Type::kCaBLE ? kCableAdvertisementUUID128 : kFidoServiceUUID);
27 
28   connection_ = std::make_unique<FidoBleConnection>(
29       adapter, std::move(address), std::move(service_uuid),
30       base::BindRepeating(&FidoBleDevice::OnStatusMessage,
31                           weak_factory_.GetWeakPtr()));
32 }
33 
FidoBleDevice(std::unique_ptr<FidoBleConnection> connection)34 FidoBleDevice::FidoBleDevice(std::unique_ptr<FidoBleConnection> connection)
35     : connection_(std::move(connection)) {}
36 
37 FidoBleDevice::~FidoBleDevice() = default;
38 
GetAddress()39 std::string FidoBleDevice::GetAddress() {
40   return connection_->address();
41 }
42 
Connect()43 void FidoBleDevice::Connect() {
44   if (state_ != State::kInit)
45     return;
46 
47   StartTimeout();
48   state_ = State::kConnecting;
49   connection_->Connect(
50       base::BindOnce(&FidoBleDevice::OnConnected, weak_factory_.GetWeakPtr()));
51 }
52 
SendPing(std::vector<uint8_t> data,DeviceCallback callback)53 void FidoBleDevice::SendPing(std::vector<uint8_t> data,
54                              DeviceCallback callback) {
55   AddToPendingFrames(FidoBleDeviceCommand::kPing, std::move(data),
56                      std::move(callback));
57 }
58 
59 // static
GetIdForAddress(const std::string & address)60 std::string FidoBleDevice::GetIdForAddress(const std::string& address) {
61   return "ble:" + address;
62 }
63 
Cancel(CancelToken token)64 void FidoBleDevice::Cancel(CancelToken token) {
65   if (current_token_ && *current_token_ == token) {
66     transaction_->Cancel();
67     return;
68   }
69 
70   for (auto it = pending_frames_.begin(); it != pending_frames_.end(); it++) {
71     if (it->token != token) {
72       continue;
73     }
74 
75     auto callback = std::move(it->callback);
76     pending_frames_.erase(it);
77     std::vector<uint8_t> cancel_reply = {
78         static_cast<uint8_t>(CtapDeviceResponseCode::kCtap2ErrKeepAliveCancel)};
79     std::move(callback).Run(
80         FidoBleFrame(FidoBleDeviceCommand::kMsg, std::move(cancel_reply)));
81     break;
82   }
83 }
84 
GetId() const85 std::string FidoBleDevice::GetId() const {
86   return GetIdForAddress(connection_->address());
87 }
88 
GetDisplayName() const89 base::string16 FidoBleDevice::GetDisplayName() const {
90   auto* device = connection_->GetBleDevice();
91   if (!device)
92     return base::string16();
93 
94   return device->GetNameForDisplay();
95 }
96 
DeviceTransport() const97 FidoTransportProtocol FidoBleDevice::DeviceTransport() const {
98   return FidoTransportProtocol::kBluetoothLowEnergy;
99 }
100 
IsInPairingMode() const101 bool FidoBleDevice::IsInPairingMode() const {
102   const BluetoothDevice* const ble_device = connection_->GetBleDevice();
103   if (!ble_device)
104     return false;
105 
106   // The spec requires exactly one of the LE Limited Discoverable Mode and LE
107   // General Discoverable Mode bits to be set to one when in pairing mode.
108   // https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#ble-advertising-format
109   const base::Optional<uint8_t> flags = ble_device->GetAdvertisingDataFlags();
110   if (flags.has_value()) {
111     const std::bitset<8> flags_set = *flags;
112     return flags_set[kLeLimitedDiscoverableModeBit] ^
113            flags_set[kLeGeneralDiscoverableModeBit];
114   }
115 
116   // Since the advertisement flags might not be available due to platform
117   // limitations, authenticators should also provide a specific pairing mode bit
118   // in FIDO's service data.
119   // https://fidoalliance.org/specs/fido-v2.0-id-20180227/fido-client-to-authenticator-protocol-v2.0-id-20180227.html#ble-pairing-authnr-considerations
120   const std::vector<uint8_t>* const fido_service_data =
121       ble_device->GetServiceDataForUUID(BluetoothUUID(kFidoServiceUUID));
122   if (!fido_service_data)
123     return false;
124 
125   return !fido_service_data->empty() &&
126          (fido_service_data->front() &
127           static_cast<uint8_t>(FidoServiceDataFlags::kPairingMode)) != 0;
128 }
129 
IsPaired() const130 bool FidoBleDevice::IsPaired() const {
131   const BluetoothDevice* const ble_device = connection_->GetBleDevice();
132   if (!ble_device)
133     return false;
134 
135   return ble_device->IsPaired();
136 }
137 
RequiresBlePairingPin() const138 bool FidoBleDevice::RequiresBlePairingPin() const {
139   const BluetoothDevice* const ble_device = connection_->GetBleDevice();
140   if (!ble_device)
141     return true;
142 
143   const std::vector<uint8_t>* const fido_service_data =
144       ble_device->GetServiceDataForUUID(BluetoothUUID(kFidoServiceUUID));
145   if (!fido_service_data)
146     return true;
147 
148   return !fido_service_data->empty() &&
149          (fido_service_data->front() &
150           static_cast<uint8_t>(FidoServiceDataFlags::kPasskeyEntry));
151 }
152 
GetReadCallbackForTesting()153 FidoBleConnection::ReadCallback FidoBleDevice::GetReadCallbackForTesting() {
154   return base::BindRepeating(&FidoBleDevice::OnStatusMessage,
155                              weak_factory_.GetWeakPtr());
156 }
157 
set_observer(FidoBleDevice::Observer * observer)158 void FidoBleDevice::set_observer(FidoBleDevice::Observer* observer) {
159   DCHECK(!observer_);
160   observer_ = observer;
161 }
162 
DeviceTransact(std::vector<uint8_t> command,DeviceCallback callback)163 FidoDevice::CancelToken FidoBleDevice::DeviceTransact(
164     std::vector<uint8_t> command,
165     DeviceCallback callback) {
166   return AddToPendingFrames(FidoBleDeviceCommand::kMsg, std::move(command),
167                             std::move(callback));
168 }
169 
GetWeakPtr()170 base::WeakPtr<FidoDevice> FidoBleDevice::GetWeakPtr() {
171   return weak_factory_.GetWeakPtr();
172 }
173 
OnResponseFrame(FrameCallback callback,base::Optional<FidoBleFrame> frame)174 void FidoBleDevice::OnResponseFrame(FrameCallback callback,
175                                     base::Optional<FidoBleFrame> frame) {
176   // The request is done, time to reset |transaction_|.
177   ResetTransaction();
178 
179   if (frame) {
180     state_ = State::kReady;
181   } else {
182     state_ = State::kDeviceError;
183   }
184   auto self = GetWeakPtr();
185   std::move(callback).Run(std::move(frame));
186   // Executing callbacks may free |this|. Check |self| first.
187   if (self)
188     Transition();
189 }
190 
ResetTransaction()191 void FidoBleDevice::ResetTransaction() {
192   transaction_.reset();
193   current_token_.reset();
194 }
195 
Transition()196 void FidoBleDevice::Transition() {
197   switch (state_) {
198     case State::kInit:
199       Connect();
200       break;
201     case State::kReady:
202       if (!pending_frames_.empty()) {
203         PendingFrame pending(std::move(pending_frames_.front()));
204         pending_frames_.pop_front();
205         current_token_ = pending.token;
206         SendRequestFrame(std::move(pending.frame), std::move(pending.callback));
207       }
208       break;
209     case State::kConnecting:
210     case State::kBusy:
211       break;
212     case State::kMsgError:
213     case State::kDeviceError:
214       auto self = GetWeakPtr();
215       // Executing callbacks may free |this|. Check |self| first.
216       while (self && !pending_frames_.empty()) {
217         // Respond to any pending frames.
218         FrameCallback cb = std::move(pending_frames_.front().callback);
219         pending_frames_.pop_front();
220         std::move(cb).Run(base::nullopt);
221       }
222       break;
223   }
224 }
225 
AddToPendingFrames(FidoBleDeviceCommand cmd,std::vector<uint8_t> request,DeviceCallback callback)226 FidoDevice::CancelToken FidoBleDevice::AddToPendingFrames(
227     FidoBleDeviceCommand cmd,
228     std::vector<uint8_t> request,
229     DeviceCallback callback) {
230   const auto token = next_cancel_token_++;
231   pending_frames_.emplace_back(
232       FidoBleFrame(cmd, std::move(request)),
233       base::BindOnce(&FidoBleDevice::OnBleResponseReceived,
234                      weak_factory_.GetWeakPtr(), std::move(callback)),
235       token);
236 
237   Transition();
238   return token;
239 }
240 
PendingFrame(FidoBleFrame in_frame,FrameCallback in_callback,CancelToken in_token)241 FidoBleDevice::PendingFrame::PendingFrame(FidoBleFrame in_frame,
242                                           FrameCallback in_callback,
243                                           CancelToken in_token)
244     : frame(std::move(in_frame)),
245       callback(std::move(in_callback)),
246       token(in_token) {}
247 
248 FidoBleDevice::PendingFrame::PendingFrame(PendingFrame&&) = default;
249 
250 FidoBleDevice::PendingFrame::~PendingFrame() = default;
251 
OnConnected(bool success)252 void FidoBleDevice::OnConnected(bool success) {
253   if (state_ != State::kConnecting) {
254     return;
255   }
256   StopTimeout();
257   if (observer_) {
258     observer_->FidoBleDeviceConnected(this, success);
259   }
260   if (!success) {
261     FIDO_LOG(ERROR) << "FidoBleDevice::Connect() failed";
262     state_ = State::kDeviceError;
263     Transition();
264     return;
265   }
266   FIDO_LOG(EVENT) << "FidoBleDevice connected";
267   DCHECK_EQ(State::kConnecting, state_);
268   StartTimeout();
269   connection_->ReadControlPointLength(base::BindOnce(
270       &FidoBleDevice::OnReadControlPointLength, weak_factory_.GetWeakPtr()));
271 }
272 
OnReadControlPointLength(base::Optional<uint16_t> length)273 void FidoBleDevice::OnReadControlPointLength(base::Optional<uint16_t> length) {
274   if (state_ == State::kDeviceError) {
275     return;
276   }
277 
278   StopTimeout();
279   if (length) {
280     control_point_length_ = *length;
281     state_ = State::kReady;
282   } else {
283     state_ = State::kDeviceError;
284   }
285   Transition();
286 }
287 
OnStatusMessage(std::vector<uint8_t> data)288 void FidoBleDevice::OnStatusMessage(std::vector<uint8_t> data) {
289   if (transaction_)
290     transaction_->OnResponseFragment(std::move(data));
291 }
292 
SendRequestFrame(FidoBleFrame frame,FrameCallback callback)293 void FidoBleDevice::SendRequestFrame(FidoBleFrame frame,
294                                      FrameCallback callback) {
295   state_ = State::kBusy;
296   transaction_.emplace(connection_.get(), control_point_length_);
297   transaction_->WriteRequestFrame(
298       std::move(frame),
299       base::BindOnce(&FidoBleDevice::OnResponseFrame,
300                      weak_factory_.GetWeakPtr(), std::move(callback)));
301 }
302 
StartTimeout()303 void FidoBleDevice::StartTimeout() {
304   timer_.Start(FROM_HERE, kDeviceTimeout, this, &FidoBleDevice::OnTimeout);
305 }
306 
StopTimeout()307 void FidoBleDevice::StopTimeout() {
308   timer_.Stop();
309 }
310 
OnTimeout()311 void FidoBleDevice::OnTimeout() {
312   FIDO_LOG(ERROR) << "FIDO BLE device timeout for " << GetId();
313   state_ = State::kDeviceError;
314   if (observer_) {
315     observer_->FidoBleDeviceTimeout(this);
316   }
317   Transition();
318 }
319 
OnBleResponseReceived(DeviceCallback callback,base::Optional<FidoBleFrame> frame)320 void FidoBleDevice::OnBleResponseReceived(DeviceCallback callback,
321                                           base::Optional<FidoBleFrame> frame) {
322   if (!frame || !frame->IsValid()) {
323     state_ = State::kDeviceError;
324     std::move(callback).Run(base::nullopt);
325     return;
326   }
327 
328   if (frame->command() == FidoBleDeviceCommand::kError) {
329     ProcessBleDeviceError(frame->data());
330     std::move(callback).Run(base::nullopt);
331     return;
332   }
333 
334   std::move(callback).Run(frame->data());
335 }
336 
ProcessBleDeviceError(base::span<const uint8_t> data)337 void FidoBleDevice::ProcessBleDeviceError(base::span<const uint8_t> data) {
338   if (data.size() != 1) {
339     FIDO_LOG(ERROR) << "Unknown BLE error received: "
340                     << base::HexEncode(data.data(), data.size());
341     state_ = State::kDeviceError;
342     return;
343   }
344 
345   switch (static_cast<FidoBleFrame::ErrorCode>(data[0])) {
346     case FidoBleFrame::ErrorCode::INVALID_CMD:
347     case FidoBleFrame::ErrorCode::INVALID_PAR:
348     case FidoBleFrame::ErrorCode::INVALID_LEN:
349       state_ = State::kMsgError;
350       break;
351     default:
352       FIDO_LOG(ERROR) << "BLE error received: " << static_cast<int>(data[0]);
353       state_ = State::kDeviceError;
354   }
355 }
356 
357 }  // namespace device
358