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