fido_cable_discovery.cc 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768
  1. // Copyright 2018 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. #include "device/fido/cable/fido_cable_discovery.h"
  5. #include <algorithm>
  6. #include <memory>
  7. #include <utility>
  8. #include "base/barrier_closure.h"
  9. #include "base/bind.h"
  10. #include "base/callback_helpers.h"
  11. #include "base/containers/contains.h"
  12. #include "base/logging.h"
  13. #include "base/metrics/histogram_functions.h"
  14. #include "base/no_destructor.h"
  15. #include "base/strings/string_number_conversions.h"
  16. #include "base/threading/sequenced_task_runner_handle.h"
  17. #include "base/time/time.h"
  18. #include "build/build_config.h"
  19. #include "components/device_event_log/device_event_log.h"
  20. #include "device/bluetooth/bluetooth_adapter_factory.h"
  21. #include "device/bluetooth/bluetooth_advertisement.h"
  22. #include "device/bluetooth/bluetooth_discovery_session.h"
  23. #include "device/bluetooth/public/cpp/bluetooth_uuid.h"
  24. #include "device/fido/cable/fido_ble_uuids.h"
  25. #include "device/fido/cable/fido_cable_handshake_handler.h"
  26. #include "device/fido/cable/fido_tunnel_device.h"
  27. #include "device/fido/features.h"
  28. #include "device/fido/fido_parsing_utils.h"
  29. #if BUILDFLAG(IS_MAC)
  30. #include "device/fido/mac/util.h"
  31. #endif
  32. namespace device {
  33. namespace {
  34. // Client name for logging in BLE scanning.
  35. constexpr char kScanClientName[] = "FIDO";
  36. // Construct advertisement data with different formats depending on client's
  37. // operating system. Ideally, we advertise EIDs as part of Service Data, but
  38. // this isn't available on all platforms. On Windows we use Manufacturer Data
  39. // instead, and on Mac our only option is to advertise an additional service
  40. // with the EID as its UUID.
  41. std::unique_ptr<BluetoothAdvertisement::Data> ConstructAdvertisementData(
  42. base::span<const uint8_t, kCableEphemeralIdSize> client_eid) {
  43. auto advertisement_data = std::make_unique<BluetoothAdvertisement::Data>(
  44. BluetoothAdvertisement::AdvertisementType::ADVERTISEMENT_TYPE_BROADCAST);
  45. #if BUILDFLAG(IS_MAC)
  46. auto list = std::make_unique<BluetoothAdvertisement::UUIDList>();
  47. list->emplace_back(kGoogleCableUUID16);
  48. list->emplace_back(fido_parsing_utils::ConvertBytesToUuid(client_eid));
  49. advertisement_data->set_service_uuids(std::move(list));
  50. #elif BUILDFLAG(IS_WIN)
  51. // References:
  52. // https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers
  53. // go/google-ble-manufacturer-data-format
  54. static constexpr uint16_t kGoogleManufacturerId = 0x00E0;
  55. static constexpr uint8_t kCableGoogleManufacturerDataType = 0x15;
  56. // Reference:
  57. // https://github.com/arnar/fido-2-specs/blob/fido-client-to-authenticator-protocol.bs#L4314
  58. static constexpr uint8_t kCableFlags = 0x20;
  59. static constexpr uint8_t kCableGoogleManufacturerDataLength =
  60. 3u + kCableEphemeralIdSize;
  61. std::array<uint8_t, 4> kCableGoogleManufacturerDataHeader = {
  62. kCableGoogleManufacturerDataLength, kCableGoogleManufacturerDataType,
  63. kCableFlags, /*version=*/1};
  64. auto manufacturer_data =
  65. std::make_unique<BluetoothAdvertisement::ManufacturerData>();
  66. std::vector<uint8_t> manufacturer_data_value;
  67. fido_parsing_utils::Append(&manufacturer_data_value,
  68. kCableGoogleManufacturerDataHeader);
  69. fido_parsing_utils::Append(&manufacturer_data_value, client_eid);
  70. manufacturer_data->emplace(kGoogleManufacturerId,
  71. std::move(manufacturer_data_value));
  72. advertisement_data->set_manufacturer_data(std::move(manufacturer_data));
  73. #elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
  74. // Reference:
  75. // https://github.com/arnar/fido-2-specs/blob/fido-client-to-authenticator-protocol.bs#L4314
  76. static constexpr uint8_t kCableFlags = 0x20;
  77. // Service data for ChromeOS and Linux is 1 byte corresponding to Cable flags,
  78. // followed by 1 byte corresponding to Cable version number, followed by 16
  79. // bytes corresponding to client EID.
  80. auto service_data = std::make_unique<BluetoothAdvertisement::ServiceData>();
  81. std::vector<uint8_t> service_data_value(18, 0);
  82. // Since the remainder of this service data field is a Cable EID, set the 5th
  83. // bit of the flag byte.
  84. service_data_value[0] = kCableFlags;
  85. service_data_value[1] = 1 /* version */;
  86. std::copy(client_eid.begin(), client_eid.end(),
  87. service_data_value.begin() + 2);
  88. service_data->emplace(kGoogleCableUUID128, std::move(service_data_value));
  89. advertisement_data->set_service_data(std::move(service_data));
  90. #endif
  91. return advertisement_data;
  92. }
  93. } // namespace
  94. // FidoCableDiscovery::CableV1DiscoveryEvent ---------------------------------
  95. // CableV1DiscoveryEvent enumerates several things that can occur during a caBLE
  96. // v1 discovery. Do not change assigned values since they are used in
  97. // histograms, only append new values. Keep synced with enums.xml.
  98. enum class FidoCableDiscovery::CableV1DiscoveryEvent : int {
  99. kStarted = 0,
  100. kAdapterPresent = 1,
  101. kAdapterAlreadyPowered = 2,
  102. kAdapterAutomaticallyPowered = 3,
  103. kAdapterManuallyPowered = 4,
  104. kAdapterPoweredOff = 5,
  105. kScanningStarted = 6,
  106. kStartScanningFailed = 12,
  107. kScanningStoppedUnexpectedly = 13,
  108. kAdvertisementRegistered = 7,
  109. kFirstCableDeviceFound = 8,
  110. kFirstCableDeviceGATTConnected = 9,
  111. kFirstCableHandshakeSucceeded = 10,
  112. kFirstCableDeviceTimeout = 11,
  113. kMaxValue = kScanningStoppedUnexpectedly,
  114. };
  115. // FidoCableDiscovery::ObservedDeviceData -------------------------------------
  116. FidoCableDiscovery::ObservedDeviceData::ObservedDeviceData() = default;
  117. FidoCableDiscovery::ObservedDeviceData::~ObservedDeviceData() = default;
  118. // FidoCableDiscovery ---------------------------------------------------------
  119. FidoCableDiscovery::FidoCableDiscovery(
  120. std::vector<CableDiscoveryData> discovery_data)
  121. : FidoDeviceDiscovery(FidoTransportProtocol::kHybrid),
  122. discovery_data_(std::move(discovery_data)) {
  123. // Windows currently does not support multiple EIDs, thus we ignore any extra
  124. // discovery data.
  125. // TODO(https://crbug.com/837088): Add support for multiple EIDs on Windows.
  126. #if BUILDFLAG(IS_WIN)
  127. if (discovery_data_.size() > 1u) {
  128. FIDO_LOG(ERROR) << "discovery_data_.size()=" << discovery_data_.size()
  129. << ", trimming to 1.";
  130. discovery_data_.erase(discovery_data_.begin() + 1, discovery_data_.end());
  131. }
  132. #endif
  133. for (const CableDiscoveryData& data : discovery_data_) {
  134. if (data.version != CableDiscoveryData::Version::V1) {
  135. continue;
  136. }
  137. has_v1_discovery_data_ = true;
  138. RecordCableV1DiscoveryEventOnce(CableV1DiscoveryEvent::kStarted);
  139. break;
  140. }
  141. }
  142. FidoCableDiscovery::~FidoCableDiscovery() {
  143. // Work around dangling advertisement references. (crbug/846522)
  144. for (auto advertisement : advertisements_) {
  145. advertisement.second->Unregister(base::DoNothing(), base::DoNothing());
  146. }
  147. if (adapter_)
  148. adapter_->RemoveObserver(this);
  149. }
  150. std::unique_ptr<FidoDeviceDiscovery::EventStream<base::span<const uint8_t, 20>>>
  151. FidoCableDiscovery::GetV2AdvertStream() {
  152. DCHECK(!advert_callback_);
  153. std::unique_ptr<EventStream<base::span<const uint8_t, 20>>> ret;
  154. std::tie(advert_callback_, ret) =
  155. EventStream<base::span<const uint8_t, 20>>::New();
  156. return ret;
  157. }
  158. std::unique_ptr<FidoCableHandshakeHandler>
  159. FidoCableDiscovery::CreateV1HandshakeHandler(
  160. FidoCableDevice* device,
  161. const CableDiscoveryData& discovery_data,
  162. const CableEidArray& authenticator_eid) {
  163. std::unique_ptr<FidoCableHandshakeHandler> handler;
  164. switch (discovery_data.version) {
  165. case CableDiscoveryData::Version::V1: {
  166. // Nonce is embedded as first 8 bytes of client EID.
  167. std::array<uint8_t, 8> nonce;
  168. const bool ok = fido_parsing_utils::ExtractArray(
  169. discovery_data.v1->client_eid, 0, &nonce);
  170. DCHECK(ok);
  171. return std::make_unique<FidoCableV1HandshakeHandler>(
  172. device, nonce, discovery_data.v1->session_pre_key);
  173. }
  174. case CableDiscoveryData::Version::V2:
  175. case CableDiscoveryData::Version::INVALID:
  176. CHECK(false);
  177. return nullptr;
  178. }
  179. }
  180. // static
  181. const BluetoothUUID& FidoCableDiscovery::GoogleCableUUID() {
  182. static const base::NoDestructor<BluetoothUUID> kUUID(kGoogleCableUUID128);
  183. return *kUUID;
  184. }
  185. const BluetoothUUID& FidoCableDiscovery::FIDOCableUUID() {
  186. static const base::NoDestructor<BluetoothUUID> kUUID(kFIDOCableUUID128);
  187. return *kUUID;
  188. }
  189. // static
  190. bool FidoCableDiscovery::IsCableDevice(const BluetoothDevice* device) {
  191. const auto& uuid1 = GoogleCableUUID();
  192. const auto& uuid2 = FIDOCableUUID();
  193. return base::Contains(device->GetServiceData(), uuid1) ||
  194. base::Contains(device->GetUUIDs(), uuid1) ||
  195. base::Contains(device->GetServiceData(), uuid2) ||
  196. base::Contains(device->GetUUIDs(), uuid2);
  197. }
  198. void FidoCableDiscovery::OnGetAdapter(scoped_refptr<BluetoothAdapter> adapter) {
  199. if (!adapter->IsPresent()) {
  200. FIDO_LOG(DEBUG) << "No BLE adapter present";
  201. NotifyDiscoveryStarted(false);
  202. return;
  203. }
  204. if (has_v1_discovery_data_) {
  205. RecordCableV1DiscoveryEventOnce(CableV1DiscoveryEvent::kAdapterPresent);
  206. }
  207. DCHECK(!adapter_);
  208. adapter_ = std::move(adapter);
  209. DCHECK(adapter_);
  210. FIDO_LOG(DEBUG) << "BLE adapter address " << adapter_->GetAddress();
  211. adapter_->AddObserver(this);
  212. if (adapter_->IsPowered()) {
  213. if (has_v1_discovery_data_) {
  214. RecordCableV1DiscoveryEventOnce(
  215. CableV1DiscoveryEvent::kAdapterAlreadyPowered);
  216. }
  217. OnSetPowered();
  218. }
  219. #if BUILDFLAG(IS_MAC)
  220. // TODO(crbug.com/1314404): turn this into a user-visible UI if we believe
  221. // that it's a good signal.
  222. switch (fido::mac::ProcessIsSigned()) {
  223. case fido::mac::CodeSigningState::kSigned:
  224. FIDO_LOG(DEBUG) << "Bluetooth authorized: "
  225. << static_cast<int>(adapter_->GetOsPermissionStatus());
  226. if (adapter_->GetOsPermissionStatus() ==
  227. BluetoothAdapter::PermissionStatus::kDenied) {
  228. observer()->BleDenied();
  229. }
  230. break;
  231. case fido::mac::CodeSigningState::kNotSigned:
  232. FIDO_LOG(DEBUG)
  233. << "Build not signed. Assuming Bluetooth permission is granted.";
  234. break;
  235. }
  236. #endif
  237. // FidoCableDiscovery blocks its transport availability callback on the
  238. // DiscoveryStarted() calls of all instantiated discoveries. Hence, this call
  239. // must not be put behind the BLE adapter getting powered on (which is
  240. // dependent on the UI), or else the UI and this discovery will wait on each
  241. // other indefinitely (see crbug.com/1018416).
  242. NotifyDiscoveryStarted(true);
  243. }
  244. void FidoCableDiscovery::OnSetPowered() {
  245. DCHECK(adapter());
  246. base::SequencedTaskRunnerHandle::Get()->PostTask(
  247. FROM_HERE, base::BindOnce(&FidoCableDiscovery::StartCableDiscovery,
  248. weak_factory_.GetWeakPtr()));
  249. }
  250. void FidoCableDiscovery::SetDiscoverySession(
  251. std::unique_ptr<BluetoothDiscoverySession> discovery_session) {
  252. discovery_session_ = std::move(discovery_session);
  253. }
  254. void FidoCableDiscovery::DeviceAdded(BluetoothAdapter* adapter,
  255. BluetoothDevice* device) {
  256. if (!IsCableDevice(device))
  257. return;
  258. CableDeviceFound(adapter, device);
  259. }
  260. void FidoCableDiscovery::DeviceChanged(BluetoothAdapter* adapter,
  261. BluetoothDevice* device) {
  262. if (!IsCableDevice(device))
  263. return;
  264. CableDeviceFound(adapter, device);
  265. }
  266. void FidoCableDiscovery::DeviceRemoved(BluetoothAdapter* adapter,
  267. BluetoothDevice* device) {
  268. const auto& device_address = device->GetAddress();
  269. if (IsCableDevice(device) &&
  270. // It only matters if V1 devices are "removed" because V2 devices do not
  271. // transport data over BLE.
  272. base::Contains(active_devices_, device_address)) {
  273. FIDO_LOG(DEBUG) << "caBLE device removed: " << device_address;
  274. RemoveDevice(FidoCableDevice::GetIdForAddress(device_address));
  275. }
  276. }
  277. void FidoCableDiscovery::AdapterPoweredChanged(BluetoothAdapter* adapter,
  278. bool powered) {
  279. if (has_v1_discovery_data_) {
  280. RecordCableV1DiscoveryEventOnce(
  281. powered ? (adapter->CanPower()
  282. ? CableV1DiscoveryEvent::kAdapterAutomaticallyPowered
  283. : CableV1DiscoveryEvent::kAdapterManuallyPowered)
  284. : CableV1DiscoveryEvent::kAdapterPoweredOff);
  285. }
  286. if (!powered) {
  287. // In order to prevent duplicate client EIDs from being advertised when
  288. // BluetoothAdapter is powered back on, unregister all existing client
  289. // EIDs.
  290. StopAdvertisements(base::DoNothing());
  291. return;
  292. }
  293. #if BUILDFLAG(IS_WIN)
  294. // On Windows, the power-on event appears to race against initialization of
  295. // the adapter, such that one of the WinRT API calls inside
  296. // BluetoothAdapter::StartDiscoverySessionWithFilter() can fail with "Device
  297. // not ready for use". So wait for things to actually be ready.
  298. // TODO(crbug/1046140): Remove this delay once the Bluetooth layer handles
  299. // the spurious failure.
  300. base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
  301. FROM_HERE,
  302. base::BindOnce(&FidoCableDiscovery::StartCableDiscovery,
  303. weak_factory_.GetWeakPtr()),
  304. base::Milliseconds(500));
  305. #else
  306. StartCableDiscovery();
  307. #endif // BUILDFLAG(IS_WIN)
  308. }
  309. void FidoCableDiscovery::AdapterDiscoveringChanged(BluetoothAdapter* adapter,
  310. bool is_scanning) {
  311. FIDO_LOG(DEBUG) << "AdapterDiscoveringChanged() is_scanning=" << is_scanning;
  312. // Ignore updates while we're not scanning for caBLE devices ourselves. Other
  313. // things in Chrome may start or stop scans at any time.
  314. if (!discovery_session_) {
  315. return;
  316. }
  317. if (has_v1_discovery_data_ && !is_scanning) {
  318. RecordCableV1DiscoveryEventOnce(
  319. CableV1DiscoveryEvent::kScanningStoppedUnexpectedly);
  320. }
  321. }
  322. void FidoCableDiscovery::FidoCableDeviceConnected(FidoCableDevice* device,
  323. bool success) {
  324. if (success) {
  325. RecordCableV1DiscoveryEventOnce(
  326. CableV1DiscoveryEvent::kFirstCableDeviceGATTConnected);
  327. }
  328. }
  329. void FidoCableDiscovery::FidoCableDeviceTimeout(FidoCableDevice* device) {
  330. RecordCableV1DiscoveryEventOnce(
  331. CableV1DiscoveryEvent::kFirstCableDeviceTimeout);
  332. }
  333. void FidoCableDiscovery::StartCableDiscovery() {
  334. adapter()->StartDiscoverySessionWithFilter(
  335. std::make_unique<BluetoothDiscoveryFilter>(
  336. BluetoothTransport::BLUETOOTH_TRANSPORT_LE),
  337. kScanClientName,
  338. base::BindOnce(&FidoCableDiscovery::OnStartDiscoverySession,
  339. weak_factory_.GetWeakPtr()),
  340. base::BindOnce(&FidoCableDiscovery::OnStartDiscoverySessionError,
  341. weak_factory_.GetWeakPtr()));
  342. }
  343. void FidoCableDiscovery::OnStartDiscoverySession(
  344. std::unique_ptr<BluetoothDiscoverySession> session) {
  345. FIDO_LOG(DEBUG) << "Discovery session started.";
  346. if (has_v1_discovery_data_) {
  347. RecordCableV1DiscoveryEventOnce(CableV1DiscoveryEvent::kScanningStarted);
  348. }
  349. SetDiscoverySession(std::move(session));
  350. // Advertising is delayed by 500ms to ensure that any UI has a chance to
  351. // appear as we don't want to start broadcasting without the user being
  352. // aware.
  353. base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
  354. FROM_HERE,
  355. base::BindOnce(&FidoCableDiscovery::StartAdvertisement,
  356. weak_factory_.GetWeakPtr()),
  357. base::Milliseconds(500));
  358. }
  359. void FidoCableDiscovery::OnStartDiscoverySessionError() {
  360. FIDO_LOG(ERROR) << "Failed to start caBLE discovery";
  361. if (has_v1_discovery_data_) {
  362. RecordCableV1DiscoveryEventOnce(
  363. CableV1DiscoveryEvent::kStartScanningFailed);
  364. }
  365. }
  366. void FidoCableDiscovery::StartAdvertisement() {
  367. DCHECK(adapter());
  368. bool advertisements_pending = false;
  369. for (const auto& data : discovery_data_) {
  370. if (data.version != CableDiscoveryData::Version::V1) {
  371. continue;
  372. }
  373. if (!advertisements_pending) {
  374. FIDO_LOG(DEBUG) << "Starting to advertise clientEIDs.";
  375. advertisements_pending = true;
  376. }
  377. adapter()->RegisterAdvertisement(
  378. ConstructAdvertisementData(data.v1->client_eid),
  379. base::BindOnce(&FidoCableDiscovery::OnAdvertisementRegistered,
  380. weak_factory_.GetWeakPtr(), data.v1->client_eid),
  381. base::BindOnce([](BluetoothAdvertisement::ErrorCode error_code) {
  382. FIDO_LOG(ERROR) << "Failed to register advertisement: " << error_code;
  383. }));
  384. }
  385. }
  386. void FidoCableDiscovery::StopAdvertisements(base::OnceClosure callback) {
  387. // Destructing a BluetoothAdvertisement invokes its Unregister() method, but
  388. // there may be references to the advertisement outside this
  389. // FidoCableDiscovery (see e.g. crbug/846522). Hence, merely clearing
  390. // |advertisements_| is not sufficient; we need to manually invoke
  391. // Unregister() for every advertisement in order to stop them. On the other
  392. // hand, |advertisements_| must not be cleared before the Unregister()
  393. // callbacks return either, in case we do hold the only reference to a
  394. // BluetoothAdvertisement.
  395. FIDO_LOG(DEBUG) << "Stopping " << advertisements_.size()
  396. << " caBLE advertisements";
  397. auto barrier_closure = base::BarrierClosure(
  398. advertisements_.size(),
  399. base::BindOnce(&FidoCableDiscovery::OnAdvertisementsStopped,
  400. weak_factory_.GetWeakPtr(), std::move(callback)));
  401. auto error_closure = base::BindRepeating(
  402. [](base::RepeatingClosure cb, BluetoothAdvertisement::ErrorCode code) {
  403. FIDO_LOG(ERROR) << "BluetoothAdvertisement::Unregister() failed: "
  404. << code;
  405. cb.Run();
  406. },
  407. barrier_closure);
  408. for (auto advertisement : advertisements_) {
  409. advertisement.second->Unregister(barrier_closure, error_closure);
  410. }
  411. }
  412. void FidoCableDiscovery::OnAdvertisementsStopped(base::OnceClosure callback) {
  413. FIDO_LOG(DEBUG) << "Advertisements stopped";
  414. advertisements_.clear();
  415. std::move(callback).Run();
  416. }
  417. void FidoCableDiscovery::OnAdvertisementRegistered(
  418. const CableEidArray& client_eid,
  419. scoped_refptr<BluetoothAdvertisement> advertisement) {
  420. FIDO_LOG(DEBUG) << "Advertisement registered";
  421. RecordCableV1DiscoveryEventOnce(
  422. CableV1DiscoveryEvent::kAdvertisementRegistered);
  423. advertisements_.emplace(client_eid, std::move(advertisement));
  424. }
  425. void FidoCableDiscovery::CableDeviceFound(BluetoothAdapter* adapter,
  426. BluetoothDevice* device) {
  427. const std::string device_address = device->GetAddress();
  428. if (base::Contains(active_devices_, device_address)) {
  429. return;
  430. }
  431. absl::optional<V1DiscoveryDataAndEID> v1_match =
  432. GetCableDiscoveryData(device);
  433. if (!v1_match) {
  434. return;
  435. }
  436. if (base::Contains(active_authenticator_eids_, v1_match->second)) {
  437. return;
  438. }
  439. active_authenticator_eids_.insert(v1_match->second);
  440. active_devices_.insert(device_address);
  441. FIDO_LOG(EVENT) << "Found new caBLEv1 device.";
  442. RecordCableV1DiscoveryEventOnce(
  443. CableV1DiscoveryEvent::kFirstCableDeviceFound);
  444. #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
  445. // Speed up GATT service discovery on ChromeOS/BlueZ.
  446. // SetConnectionLatency() is NOTIMPLEMENTED() on other platforms.
  447. device->SetConnectionLatency(BluetoothDevice::CONNECTION_LATENCY_LOW,
  448. base::DoNothing(), base::BindOnce([]() {
  449. FIDO_LOG(ERROR)
  450. << "SetConnectionLatency() failed";
  451. }));
  452. #endif // BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX)
  453. auto cable_device =
  454. std::make_unique<FidoCableDevice>(adapter, device_address);
  455. cable_device->set_observer(this);
  456. std::unique_ptr<FidoCableHandshakeHandler> handshake_handler =
  457. CreateV1HandshakeHandler(cable_device.get(), v1_match->first,
  458. v1_match->second);
  459. auto* const handshake_handler_ptr = handshake_handler.get();
  460. active_handshakes_.emplace_back(std::move(cable_device),
  461. std::move(handshake_handler));
  462. StopAdvertisements(
  463. base::BindOnce(&FidoCableDiscovery::ConductEncryptionHandshake,
  464. weak_factory_.GetWeakPtr(), handshake_handler_ptr,
  465. v1_match->first.version));
  466. }
  467. void FidoCableDiscovery::ConductEncryptionHandshake(
  468. FidoCableHandshakeHandler* handshake_handler,
  469. CableDiscoveryData::Version cable_version) {
  470. handshake_handler->InitiateCableHandshake(base::BindOnce(
  471. &FidoCableDiscovery::ValidateAuthenticatorHandshakeMessage,
  472. weak_factory_.GetWeakPtr(), cable_version, handshake_handler));
  473. }
  474. void FidoCableDiscovery::ValidateAuthenticatorHandshakeMessage(
  475. CableDiscoveryData::Version cable_version,
  476. FidoCableHandshakeHandler* handshake_handler,
  477. absl::optional<std::vector<uint8_t>> handshake_response) {
  478. const bool ok = handshake_response.has_value() &&
  479. handshake_handler->ValidateAuthenticatorHandshakeMessage(
  480. *handshake_response);
  481. bool found = false;
  482. for (auto it = active_handshakes_.begin(); it != active_handshakes_.end();
  483. it++) {
  484. if (it->second.get() != handshake_handler) {
  485. continue;
  486. }
  487. found = true;
  488. if (ok) {
  489. AddDevice(std::move(it->first));
  490. }
  491. active_handshakes_.erase(it);
  492. break;
  493. }
  494. DCHECK(found);
  495. if (ok) {
  496. FIDO_LOG(DEBUG) << "Authenticator handshake validated";
  497. if (cable_version == CableDiscoveryData::Version::V1) {
  498. RecordCableV1DiscoveryEventOnce(
  499. CableV1DiscoveryEvent::kFirstCableHandshakeSucceeded);
  500. }
  501. } else {
  502. FIDO_LOG(DEBUG) << "Authenticator handshake invalid";
  503. }
  504. }
  505. absl::optional<FidoCableDiscovery::V1DiscoveryDataAndEID>
  506. FidoCableDiscovery::GetCableDiscoveryData(const BluetoothDevice* device) {
  507. const std::vector<uint8_t>* service_data =
  508. device->GetServiceDataForUUID(GoogleCableUUID());
  509. if (!service_data) {
  510. service_data = device->GetServiceDataForUUID(FIDOCableUUID());
  511. }
  512. absl::optional<CableEidArray> maybe_eid_from_service_data =
  513. MaybeGetEidFromServiceData(device);
  514. std::vector<CableEidArray> uuids = GetUUIDs(device);
  515. const std::string address = device->GetAddress();
  516. const auto it = observed_devices_.find(address);
  517. const bool known = it != observed_devices_.end();
  518. if (known) {
  519. std::unique_ptr<ObservedDeviceData>& data = it->second;
  520. if (maybe_eid_from_service_data == data->service_data &&
  521. uuids == data->uuids) {
  522. // Duplicate data. Ignore.
  523. return absl::nullopt;
  524. }
  525. }
  526. // New or updated device information.
  527. if (known) {
  528. FIDO_LOG(DEBUG) << "Updated information for caBLE device " << address
  529. << ":";
  530. } else {
  531. FIDO_LOG(DEBUG) << "New caBLE device " << address << ":";
  532. }
  533. absl::optional<FidoCableDiscovery::V1DiscoveryDataAndEID> result;
  534. if (maybe_eid_from_service_data.has_value()) {
  535. result =
  536. GetCableDiscoveryDataFromAuthenticatorEid(*maybe_eid_from_service_data);
  537. FIDO_LOG(DEBUG) << " Service data: "
  538. << ResultDebugString(*maybe_eid_from_service_data, result);
  539. } else if (service_data) {
  540. FIDO_LOG(DEBUG) << " Service data: " << base::HexEncode(*service_data);
  541. } else {
  542. FIDO_LOG(DEBUG) << " Service data: <none>";
  543. }
  544. std::array<uint8_t, 16 + 4> v2_advert;
  545. if (advert_callback_ && service_data &&
  546. service_data->size() == v2_advert.size()) {
  547. memcpy(v2_advert.data(), service_data->data(), v2_advert.size());
  548. advert_callback_.Run(v2_advert);
  549. }
  550. auto observed_data = std::make_unique<ObservedDeviceData>();
  551. observed_data->service_data = maybe_eid_from_service_data;
  552. observed_data->uuids = uuids;
  553. observed_devices_.insert_or_assign(address, std::move(observed_data));
  554. return result;
  555. }
  556. // static
  557. absl::optional<CableEidArray> FidoCableDiscovery::MaybeGetEidFromServiceData(
  558. const BluetoothDevice* device) {
  559. const std::vector<uint8_t>* service_data =
  560. device->GetServiceDataForUUID(GoogleCableUUID());
  561. if (!service_data) {
  562. return absl::nullopt;
  563. }
  564. // Received service data from authenticator must have a flag that signals that
  565. // the service data includes Cable EID.
  566. if (service_data->empty() || !(service_data->at(0) >> 5 & 1u))
  567. return absl::nullopt;
  568. CableEidArray received_authenticator_eid;
  569. bool extract_success = fido_parsing_utils::ExtractArray(
  570. *service_data, 2, &received_authenticator_eid);
  571. if (!extract_success)
  572. return absl::nullopt;
  573. return received_authenticator_eid;
  574. }
  575. // static
  576. std::vector<CableEidArray> FidoCableDiscovery::GetUUIDs(
  577. const BluetoothDevice* device) {
  578. std::vector<CableEidArray> ret;
  579. const auto service_uuids = device->GetUUIDs();
  580. for (const auto& uuid : service_uuids) {
  581. std::vector<uint8_t> uuid_binary = uuid.GetBytes();
  582. CableEidArray authenticator_eid;
  583. DCHECK_EQ(authenticator_eid.size(), uuid_binary.size());
  584. memcpy(authenticator_eid.data(), uuid_binary.data(),
  585. std::min(uuid_binary.size(), authenticator_eid.size()));
  586. ret.emplace_back(std::move(authenticator_eid));
  587. }
  588. return ret;
  589. }
  590. absl::optional<FidoCableDiscovery::V1DiscoveryDataAndEID>
  591. FidoCableDiscovery::GetCableDiscoveryDataFromAuthenticatorEid(
  592. CableEidArray authenticator_eid) {
  593. for (const auto& candidate : discovery_data_) {
  594. if (candidate.version == CableDiscoveryData::Version::V1 &&
  595. candidate.MatchV1(authenticator_eid)) {
  596. return V1DiscoveryDataAndEID(candidate, authenticator_eid);
  597. }
  598. }
  599. return absl::nullopt;
  600. }
  601. void FidoCableDiscovery::RecordCableV1DiscoveryEventOnce(
  602. CableV1DiscoveryEvent event) {
  603. DCHECK(has_v1_discovery_data_);
  604. if (base::Contains(recorded_events_, event)) {
  605. return;
  606. }
  607. recorded_events_.insert(event);
  608. base::UmaHistogramEnumeration("WebAuthentication.CableV1DiscoveryEvent",
  609. event);
  610. }
  611. void FidoCableDiscovery::StartInternal() {
  612. BluetoothAdapterFactory::Get()->GetAdapter(base::BindOnce(
  613. &FidoCableDiscovery::OnGetAdapter, weak_factory_.GetWeakPtr()));
  614. }
  615. // static
  616. std::string FidoCableDiscovery::ResultDebugString(
  617. const CableEidArray& eid,
  618. const absl::optional<FidoCableDiscovery::V1DiscoveryDataAndEID>& result) {
  619. static const uint8_t kAppleContinuity[16] = {
  620. 0xd0, 0x61, 0x1e, 0x78, 0xbb, 0xb4, 0x45, 0x91,
  621. 0xa5, 0xf8, 0x48, 0x79, 0x10, 0xae, 0x43, 0x66,
  622. };
  623. static const uint8_t kAppleUnknown[16] = {
  624. 0x9f, 0xa4, 0x80, 0xe0, 0x49, 0x67, 0x45, 0x42,
  625. 0x93, 0x90, 0xd3, 0x43, 0xdc, 0x5d, 0x04, 0xae,
  626. };
  627. static const uint8_t kAppleMedia[16] = {
  628. 0x89, 0xd3, 0x50, 0x2b, 0x0f, 0x36, 0x43, 0x3a,
  629. 0x8e, 0xf4, 0xc5, 0x02, 0xad, 0x55, 0xf8, 0xdc,
  630. };
  631. static const uint8_t kAppleNotificationCenter[16] = {
  632. 0x79, 0x05, 0xf4, 0x31, 0xb5, 0xce, 0x4e, 0x99,
  633. 0xa4, 0x0f, 0x4b, 0x1e, 0x12, 0x2d, 0x00, 0xd0,
  634. };
  635. static const uint8_t kCable[16] = {
  636. 0x00, 0x00, 0xfd, 0xe2, 0x00, 0x00, 0x10, 0x00,
  637. 0x80, 0x00, 0x00, 0x80, 0x5f, 0x9b, 0x34, 0xfb,
  638. };
  639. std::string ret = base::HexEncode(eid) + "";
  640. if (!result) {
  641. // Try to identify some common UUIDs that are random and thus otherwise look
  642. // like potential EIDs.
  643. if (memcmp(eid.data(), kAppleContinuity, eid.size()) == 0) {
  644. ret += " (Apple Continuity service)";
  645. } else if (memcmp(eid.data(), kAppleUnknown, eid.size()) == 0) {
  646. ret += " (Apple service)";
  647. } else if (memcmp(eid.data(), kAppleMedia, eid.size()) == 0) {
  648. ret += " (Apple Media service)";
  649. } else if (memcmp(eid.data(), kAppleNotificationCenter, eid.size()) == 0) {
  650. ret += " (Apple Notification service)";
  651. } else if (memcmp(eid.data(), kCable, eid.size()) == 0) {
  652. ret += " (caBLE indicator)";
  653. }
  654. return ret;
  655. }
  656. if (result) {
  657. ret += " (version one match)";
  658. }
  659. return ret;
  660. }
  661. bool FidoCableDiscovery::MaybeStop() {
  662. if (!FidoDeviceDiscovery::MaybeStop()) {
  663. NOTREACHED();
  664. }
  665. StopAdvertisements(base::DoNothing());
  666. return true;
  667. }
  668. } // namespace device